Compare commits
94 Commits
9503fbe78a
...
6a54757c6a
Author | SHA1 | Date | |
---|---|---|---|
6a54757c6a | |||
4153a936a6 | |||
4ad08db846 | |||
25f4bc6923 | |||
f7b8478337 | |||
d8b72ef190 | |||
49d1bd013b | |||
06502ebaa0 | |||
fdaac5e1c0 | |||
2a1917942d | |||
|
62d0ca6076 | ||
|
1094b3851f | ||
|
b956b4ef79 | ||
|
99898555d4 | ||
|
57555751e4 | ||
|
f2b194f712 | ||
|
f010287bfa | ||
|
d62c26bee3 | ||
|
123bf8051f | ||
|
4863b4c77c | ||
|
df372ad14e | ||
|
8736155e64 | ||
|
435700cc4f | ||
|
871e33990a | ||
87de44f7f4 | |||
7886f260d4 | |||
32022fe61b | |||
24c94a04ad | |||
4d681f195e | |||
56098b5699 | |||
c110dc9ad6 | |||
|
efd61b30f4 | ||
|
84096be043 | ||
|
4b7c69ea36 | ||
|
f5ca14f746 | ||
576e950650 | |||
982c2221e2 | |||
aa3c7e1350 | |||
7a269b0215 | |||
999df303ea | |||
85de430b09 | |||
1850ffb727 | |||
|
6a94750c05 | ||
|
acc8aeed2c | ||
|
1894bee556 | ||
f808c018fd | |||
|
b101831c53 | ||
|
6791da7d8e | ||
|
4cf5e8ce18 | ||
ace9b39fe3 | |||
0111453f06 | |||
|
f876ee7bbe | ||
|
db8621ec5c | ||
07c3808122 | |||
|
b28c288665 | ||
|
ca958bb2cb | ||
|
fd9a5555dc | ||
|
7733f88dae | ||
|
1a85e9d13e | ||
|
bab907a1e2 | ||
|
a506b4dcc1 | ||
|
45e4415cec | ||
|
57e4ba69d9 | ||
|
88bbf16699 | ||
|
d46ced7614 | ||
|
044617580c | ||
|
54a46e2fb4 | ||
|
28938aba66 | ||
|
8e645226b8 | ||
97f05d2c5c | |||
8d73e0d48b | |||
|
4b5c160e94 | ||
|
ebd567a757 | ||
18871db6b6 | |||
|
1ae317dbb3 | ||
|
e4354d42cd | ||
|
8bba2d64af | ||
|
d4e52152ef | ||
|
bdf07155c8 | ||
|
b905db0cfa | ||
|
3b6d998ce1 | ||
|
5340461a7e | ||
|
45eec2ff22 | ||
|
9c34066128 | ||
|
628894a254 | ||
92fce58429 | |||
|
2e998a1174 | ||
|
60a2fb996b | ||
b282562cdd | |||
d0abf7d8ab | |||
|
409af72039 | ||
|
799599bd83 | ||
ec18d85546 | |||
|
a97f293a6c |
9309
package-lock.json
generated
9309
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -24,8 +24,11 @@
|
|||||||
"@vicons/ionicons5": "^0.13.0",
|
"@vicons/ionicons5": "^0.13.0",
|
||||||
"@vueup/vue-quill": "^1.2.0",
|
"@vueup/vue-quill": "^1.2.0",
|
||||||
"@vueuse/core": "^10.7.0",
|
"@vueuse/core": "^10.7.0",
|
||||||
|
"@vueuse/rxjs": "^13.4.0",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"dexie": "^4.0.11",
|
||||||
"highlight.js": "^11.5.0",
|
"highlight.js": "^11.5.0",
|
||||||
"js-audio-recorder": "^1.0.7",
|
"js-audio-recorder": "^1.0.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
@ -35,6 +38,7 @@
|
|||||||
"quill": "^1.3.7",
|
"quill": "^1.3.7",
|
||||||
"quill-image-uploader": "^1.3.0",
|
"quill-image-uploader": "^1.3.0",
|
||||||
"quill-mention": "^4.1.0",
|
"quill-mention": "^4.1.0",
|
||||||
|
"rxjs": "^7.8.2",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6",
|
||||||
"viewerjs": "^1.11.7",
|
"viewerjs": "^1.11.7",
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
|
1450
pnpm-lock.yaml
1450
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -99,3 +99,23 @@ export const ServeEmptyMessage = (data) => {
|
|||||||
export const ServeMessageReadDetail = (data) => {
|
export const ServeMessageReadDetail = (data) => {
|
||||||
return post('/api/v1/talk/my-records/read/condition', data)
|
return post('/api/v1/talk/my-records/read/condition', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 主动添加好友(单向好友)
|
||||||
|
export const ServeAddFriend = (data) => {
|
||||||
|
return post('/api/v1/contact/friend/add', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测是否需要加好友
|
||||||
|
export const ServeCheckFriend = (data) => {
|
||||||
|
return post('/api/v1/contact/friend/check', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测是否需要加好友
|
||||||
|
export const GetContactFriendList = (data) => {
|
||||||
|
return post('/api/v1/contact/friend/list', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索好友
|
||||||
|
export const GetFriendList = (data) => {
|
||||||
|
return post('/api/v1/contact/friend/search', data)
|
||||||
|
}
|
@ -2,12 +2,14 @@ import { post, get, upload } from '@/utils/request'
|
|||||||
|
|
||||||
//ES搜索-主页搜索什么都有、指定用户、指定群、群与用户概览
|
//ES搜索-主页搜索什么都有、指定用户、指定群、群与用户概览
|
||||||
export const ServeSeachQueryAll = (data = {}) => {
|
export const ServeSeachQueryAll = (data = {}) => {
|
||||||
return post('/api/v1/elasticsearch/query-all/v2', data)
|
return post('/api/v1/elasticsearch/query-all', data)
|
||||||
|
// return post('/api/v1/elasticsearch/query-all/v2', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ES搜索用户数据
|
// ES搜索用户数据
|
||||||
export const ServeQueryUser = (data) => {
|
export const ServeQueryUser = (data) => {
|
||||||
return post('/api/v1/elasticsearch/query-user/v2', data)
|
return post('/api/v1/elasticsearch/query-user', data)
|
||||||
|
// return post('/api/v1/elasticsearch/query-user/v2', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ES搜索群组数据
|
// ES搜索群组数据
|
||||||
|
BIN
src/assets/image/bofang.png
Normal file
BIN
src/assets/image/bofang.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/image/yuyin.png
Normal file
BIN
src/assets/image/yuyin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
File diff suppressed because it is too large
Load Diff
@ -72,8 +72,9 @@ const formatTime = (value: number = 0) => {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="pointer w-200px bg-#f5f5f5 rounded-10px px-11px">
|
<div class="pointer w-200px bg-#F3F4FD rounded-10px px-11px">
|
||||||
<div class="im-message-audio h-44px">
|
<div class="im-message-audio h-44px">
|
||||||
|
<aTrumpet :isPlay="false" color="black" :size="30"></aTrumpet>
|
||||||
<audio
|
<audio
|
||||||
ref="audioRef"
|
ref="audioRef"
|
||||||
preload="auto"
|
preload="auto"
|
||||||
@ -87,23 +88,37 @@ const formatTime = (value: number = 0) => {
|
|||||||
|
|
||||||
<div class="play">
|
<div class="play">
|
||||||
<div class="btn pointer" @click.stop="onPlay">
|
<div class="btn pointer" @click.stop="onPlay">
|
||||||
<n-icon :size="18" :component="state.isAudioPlay ? PauseOne : PlayOne" />
|
<!-- <n-icon :size="18" :component="state.isAudioPlay ? PauseOne : PlayOne" /> -->
|
||||||
|
<img
|
||||||
|
v-if="!state.isAudioPlay"
|
||||||
|
src="@/assets/image/yuyin.png"
|
||||||
|
class="w-[16px] h-[16px]"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<img v-else src="@/assets/image/bofang.png" class="w-[16px] h-[16px]" alt="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="desc">
|
<!-- <div class="desc">
|
||||||
<span class="line" v-for="i in 23" :key="i"></span>
|
<span class="line" v-for="i in 23" :key="i"></span>
|
||||||
<span
|
<span
|
||||||
class="indicator"
|
class="indicator"
|
||||||
:style="{ left: state.progress + '%' }"
|
:style="{ left: state.progress + '%' }"
|
||||||
v-show="state.progress > 0"
|
v-show="state.progress > 0"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="time">{{ durationDesc }}</div>
|
<!-- <div class="time">{{ durationDesc }}</div> -->
|
||||||
|
<div>{{ durationDesc.split('"')[0] }}s</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="expand">
|
<transition name="expand">
|
||||||
<div class="text-container py-12px border-t-2px border-t-solid border-t-#E0E0E4" v-if="data.is_convert_text===1">
|
<div
|
||||||
<div class="flex justify-center items-center" v-if="data.is_convert_text===1&&!data.extra.content">
|
class="text-container py-12px border-t-1px border-t-solid border-t-#E2E2EB"
|
||||||
|
v-if="data.is_convert_text === 1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex justify-center items-center"
|
||||||
|
v-if="data.is_convert_text === 1 && !data.extra.content"
|
||||||
|
>
|
||||||
<n-spin :stroke-width="3" size="small" />
|
<n-spin :stroke-width="3" size="small" />
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
@ -112,7 +127,6 @@ const formatTime = (value: number = 0) => {
|
|||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.im-message-audio {
|
.im-message-audio {
|
||||||
@ -135,7 +149,7 @@ const formatTime = (value: number = 0) => {
|
|||||||
.btn {
|
.btn {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
background-color: var(--audio-btn-bg-color);
|
// background-color: var(--audio-btn-bg-color);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
color: rgb(24, 24, 24);
|
color: rgb(24, 24, 24);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -62,6 +62,15 @@ const fileInfo = computed(() => {
|
|||||||
return fileTypes[extension] || fileTypes.DEFAULT
|
return fileTypes[extension] || fileTypes.DEFAULT
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 判断文件是否可以预览
|
||||||
|
const canPreview = computed(() => {
|
||||||
|
const extension = getFileExtension(props.extra.path)
|
||||||
|
return extension === 'PDF' ||
|
||||||
|
EXCEL_EXTENSIONS.includes(extension) ||
|
||||||
|
WORD_EXTENSIONS.includes(extension) ||
|
||||||
|
PPT_EXTENSIONS.includes(extension)
|
||||||
|
})
|
||||||
|
|
||||||
// 获取文件扩展名
|
// 获取文件扩展名
|
||||||
function getFileExtension(filepath) {
|
function getFileExtension(filepath) {
|
||||||
const parts = filepath?.split('.')
|
const parts = filepath?.split('.')
|
||||||
@ -86,15 +95,20 @@ const strokeDashoffset = computed(() =>
|
|||||||
|
|
||||||
// 处理文件点击事件
|
// 处理文件点击事件
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
|
// 只有在不上传中且文件类型支持预览时才打开预览窗口
|
||||||
if(!props.extra.is_uploading) {
|
if(!props.extra.is_uploading) {
|
||||||
|
if(canPreview.value){
|
||||||
window.open(
|
window.open(
|
||||||
`${import.meta.env.VITE_PAGE_URL}/office?url=${props.extra.path}`,
|
`${import.meta.env.VITE_PAGE_URL}/office?url=${props.extra.path}`,
|
||||||
'_blank',
|
'_blank',
|
||||||
'width=1200,height=900,left=200,top=200,toolbar=no,menubar=no,scrollbars=yes,resizable=yes,location=no,status=no'
|
'width=1200,height=900,left=200,top=200,toolbar=no,menubar=no,scrollbars=yes,resizable=yes,location=no,status=no'
|
||||||
);
|
);
|
||||||
|
}else{
|
||||||
|
window['$message'].warning('暂不支持在线预览该类型文件')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function downloadFileWithProgress(resourceUrl, filename) {
|
function downloadFileWithProgress(resourceUrl, filename) {
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
@ -114,7 +128,7 @@ const handleDownload = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="file-message flex flex-col" @click="handleClick">
|
<div class="file-message flex flex-col can-preview" @click="handleClick">
|
||||||
<!-- 文件头部信息 -->
|
<!-- 文件头部信息 -->
|
||||||
<div class="file-header">
|
<div class="file-header">
|
||||||
<!-- 文件名 -->
|
<!-- 文件名 -->
|
||||||
@ -184,7 +198,14 @@ const handleDownload = () => {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.can-preview {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-header {
|
.file-header {
|
||||||
|
@ -38,7 +38,7 @@ const img = (src: string, width = 200) => {
|
|||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<n-image class="h-149px" :src="extra.url" />
|
<n-image class="h-149px" :src="extra.url" />
|
||||||
<!-- 上传中的loading蒙版 -->
|
<!-- 上传中的loading蒙版 -->
|
||||||
<div v-if="props.extra.is_uploading" class="loading-overlay">
|
<div v-if="extra.is_uploading" class="loading-overlay">
|
||||||
<n-spin size="large" />
|
<n-spin size="large" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -53,7 +53,7 @@ const img = (src: string, width = 200) => {
|
|||||||
height:149px;
|
height:149px;
|
||||||
|
|
||||||
&.left {
|
&.left {
|
||||||
background: var(--im-message-right-bg-color);
|
background: #F4F4FC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
|
@ -115,13 +115,7 @@ const onRevoke = () => {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div style="display: inline-block;" v-if="login_uid === user_id">
|
<div style="display: inline-block;" v-if="login_uid === user_id">
|
||||||
<n-button
|
<n-button @click="onRevoke" v-if="data.msg_type === 1&&data.extra?.content&&data.is_self_action" text class="text-#46299D text-11px">重新编辑</n-button>
|
||||||
@click="onRevoke"
|
|
||||||
v-if="data.msg_type === 1 && data.extra?.content"
|
|
||||||
text
|
|
||||||
class="text-#46299D text-11px"
|
|
||||||
>重新编辑</n-button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <span v-if="login_uid == user_idA"> 你撤回B了一条消息 | {{ formatTime(datetime) }} </span>
|
<!-- <span v-if="login_uid == user_idA"> 你撤回B了一条消息 | {{ formatTime(datetime) }} </span>
|
||||||
<span v-else-if="login_uid == user_idB"> A撤回你了一条消息 | {{ formatTime(datetime) }} </span>
|
<span v-else-if="login_uid == user_idB"> A撤回你了一条消息 | {{ formatTime(datetime) }} </span>
|
||||||
@ -142,13 +136,7 @@ const onRevoke = () => {
|
|||||||
</span>
|
</span>
|
||||||
<span v-if="talk_type === 2 && extra"> {{ extra }} | {{ formatTime(datetime) }} </span>
|
<span v-if="talk_type === 2 && extra"> {{ extra }} | {{ formatTime(datetime) }} </span>
|
||||||
<div style="display: inline-block;" v-if="login_uid === user_id">
|
<div style="display: inline-block;" v-if="login_uid === user_id">
|
||||||
<n-button
|
<n-button @click="onRevoke" v-if="data.msg_type === 1&&data.extra?.content&&data.is_self_action" text class="text-#46299D text-11px">重新编辑</n-button>
|
||||||
@click="onRevoke"
|
|
||||||
v-if="data.msg_type === 1 && data.extra?.content"
|
|
||||||
text
|
|
||||||
class="text-#46299D text-11px"
|
|
||||||
>重新编辑</n-button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import 'xgplayer/dist/index.min.css'
|
import 'xgplayer/dist/index.min.css'
|
||||||
import { ref, nextTick, watch } from 'vue'
|
import { ref, nextTick, watch, computed } from 'vue'
|
||||||
import { NImage, NModal, NCard, NProgress, NPopconfirm } from 'naive-ui'
|
import { NImage, NModal, NCard, NProgress, NPopconfirm } from 'naive-ui'
|
||||||
import { Play, Close, Pause, Right, Attention } from '@icon-park/vue-next'
|
import { Play, Close, Pause, Right, Attention } from '@icon-park/vue-next'
|
||||||
import { getImageInfo } from '@/utils/functions'
|
import { getImageInfo } from '@/utils/functions'
|
||||||
@ -64,6 +64,11 @@ const updatePauseStatus = () => {
|
|||||||
// 初始化时检查状态
|
// 初始化时检查状态
|
||||||
updatePauseStatus()
|
updatePauseStatus()
|
||||||
|
|
||||||
|
// 创建视频封面的URL
|
||||||
|
const videoSrc = computed(() => {
|
||||||
|
// 即使在上传过程中也返回视频URL,这样可以显示视频封面
|
||||||
|
return props.extra.url || ''
|
||||||
|
})
|
||||||
// // 监听关键道具变化
|
// // 监听关键道具变化
|
||||||
// watch(() => props.extra.percentage, (newVal: number | undefined) => {
|
// watch(() => props.extra.percentage, (newVal: number | undefined) => {
|
||||||
// // 确保进度更新时 UI 也实时更新
|
// // 确保进度更新时 UI 也实时更新
|
||||||
@ -136,7 +141,7 @@ function resumeUpload(e) {
|
|||||||
>
|
>
|
||||||
|
|
||||||
<!-- <n-image :src="extra.cover" preview-disabled /> -->
|
<!-- <n-image :src="extra.cover" preview-disabled /> -->
|
||||||
<video :src="props.extra.url" :controls="false"></video>
|
<video :src="videoSrc" :controls="false"></video>
|
||||||
<!-- 上传进度时的黑色半透明蒙层 -->
|
<!-- 上传进度时的黑色半透明蒙层 -->
|
||||||
<div v-if="extra.is_uploading && !uploadFailed" class="upload-mask"></div>
|
<div v-if="extra.is_uploading && !uploadFailed" class="upload-mask"></div>
|
||||||
<!-- 上传进度显示 -->
|
<!-- 上传进度显示 -->
|
||||||
@ -252,7 +257,7 @@ function resumeUpload(e) {
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.45);
|
background: rgba(0, 0, 0, 0.3); /* 降低不透明度,从0.45改为0.3,让视频封面能够显示 */
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ const { showUserInfoModal } = useInject()
|
|||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
|
|
||||||
<template v-for="(user, index) in extra.members" :key="index">
|
<template v-for="(user, index) in extra.members" :key="index">
|
||||||
<a @click="showUserInfoModal(user.erp_user_id,user.user_id)">{{ user.nickname }}</a>
|
<a>{{ user.nickname }}</a>
|
||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ defineProps({
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a >
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -14,14 +14,14 @@ const { showUserInfoModal } = useInject()
|
|||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
|
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a >
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span>创建了群聊,并邀请了</span>
|
<span>创建了群聊,并邀请了</span>
|
||||||
|
|
||||||
<template v-for="(user, index) in extra.members" :key="index">
|
<template v-for="(user, index) in extra.members" :key="index">
|
||||||
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
|
<a >{{ user.nickname }}</a>
|
||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@ defineProps({
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(data.user_id)">
|
<a>
|
||||||
<!-- {{ data.nickname }} -->
|
<!-- {{ data.nickname }} -->
|
||||||
管理员
|
管理员
|
||||||
</a>
|
</a>
|
||||||
|
@ -13,14 +13,14 @@ const { showUserInfoModal } = useInject()
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a >
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span>邀请了</span>
|
<span>邀请了</span>
|
||||||
|
|
||||||
<template v-for="(user, index) in extra.members" :key="index">
|
<template v-for="(user, index) in extra.members" :key="index">
|
||||||
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
|
<a>{{ user.nickname }}</a>
|
||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -13,14 +13,14 @@ const { showUserInfoModal } = useInject()
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a >
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span>解除了</span>
|
<span>解除了</span>
|
||||||
|
|
||||||
<template v-for="(user, index) in extra.members" :key="index">
|
<template v-for="(user, index) in extra.members" :key="index">
|
||||||
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
|
<a >{{ user.nickname }}</a>
|
||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -13,14 +13,14 @@ const { showUserInfoModal } = useInject()
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a>
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span>将</span>
|
<span>将</span>
|
||||||
|
|
||||||
<template v-for="(user, index) in extra.members" :key="index">
|
<template v-for="(user, index) in extra.members" :key="index">
|
||||||
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
|
<a>{{ user.nickname }}</a>
|
||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -13,14 +13,14 @@ const { showUserInfoModal } = useInject()
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a>
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span>设置了</span>
|
<span>设置了</span>
|
||||||
|
|
||||||
<template v-for="(user, index) in extra.members" :key="index">
|
<template v-for="(user, index) in extra.members" :key="index">
|
||||||
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
|
<a>{{ user.nickname }}</a>
|
||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ const { showUserInfoModal } = useInject()
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a >
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ defineProps({
|
|||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<template v-for="(user, index) in extra?.members" :key="index">
|
<template v-for="(user, index) in extra?.members" :key="index">
|
||||||
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
|
<a >{{ user.nickname }}</a>
|
||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
<span>已离开此群</span>
|
<span>已离开此群</span>
|
||||||
|
@ -13,7 +13,7 @@ const { showUserInfoModal } = useInject()
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a >
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ const { showUserInfoModal } = useInject()
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
<a @click="showUserInfoModal(extra.old_owner_id)">{{ extra.old_owner_name }}</a>
|
<a >{{ extra.old_owner_name }}</a>
|
||||||
<span>将群主转让给</span>
|
<span>将群主转让给</span>
|
||||||
<a @click="showUserInfoModal(extra.new_owner_id)">{{ extra.new_owner_name }}</a>
|
<a >{{ extra.new_owner_name }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
color: #979191;
|
color: #979191;
|
||||||
user-select: none;
|
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -23,13 +22,11 @@
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
color: #939596;
|
color: #939596;
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #462AA0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,8 +131,6 @@ const onSubmit = () => {
|
|||||||
talk_type: item.talk_type
|
talk_type: item.talk_type
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log('data', data);
|
|
||||||
console.log('checkedFilter.value', checkedFilter.value);
|
|
||||||
emit('on-submit', data)
|
emit('on-submit', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,9 +8,17 @@ import { useTalkStore } from '@/store'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import xNModal from '@/components/x-naive-ui/x-n-modal/index.vue'
|
import xNModal from '@/components/x-naive-ui/x-n-modal/index.vue'
|
||||||
import { NSkeleton } from 'naive-ui'
|
import { NSkeleton } from 'naive-ui'
|
||||||
|
import { ServeCheckFriend, ServeAddFriend } from '@/api/chat'
|
||||||
|
import { useUtil } from '@/hooks/useUtil'
|
||||||
|
|
||||||
|
const { useMessage } = useUtil()
|
||||||
|
|
||||||
|
// const isFriend = ref(false) // 是否是我的好友
|
||||||
|
// const showBtn = ref(false)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const talkStore = useTalkStore()
|
const talkStore = useTalkStore()
|
||||||
const emit = defineEmits(['update:show', 'update:uid', 'updateRemark'])
|
const emit = defineEmits(['update:show', 'update:uid', 'updateRemark', 'update:send'])
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -86,6 +94,7 @@ const onLoadData = () => {
|
|||||||
const onToTalk = () => {
|
const onToTalk = () => {
|
||||||
talkStore.toTalk(1, props.uid, router)
|
talkStore.toTalk(1, props.uid, router)
|
||||||
emit('update:show', false)
|
emit('update:show', false)
|
||||||
|
emit('update:send')
|
||||||
}
|
}
|
||||||
|
|
||||||
// const onJoinContact = () => {
|
// const onJoinContact = () => {
|
||||||
@ -167,8 +176,27 @@ const onToTalk = () => {
|
|||||||
// emit('update:show', value)
|
// emit('update:show', value)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 添加好友
|
||||||
|
// const addFriend = () => {
|
||||||
|
// let params = {
|
||||||
|
// receiver_id: props.uid, //聊天的用户id
|
||||||
|
// talk_type: 1
|
||||||
|
// }
|
||||||
|
// ServeAddFriend(params).then((res) => {
|
||||||
|
// if (res?.code === 200) {
|
||||||
|
// useMessage.success('添加成功')
|
||||||
|
// isFriend.value = !isFriend.value
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
const onAfterEnter = () => {
|
const onAfterEnter = () => {
|
||||||
onLoadData()
|
onLoadData()
|
||||||
|
// ServeCheckFriend({ receiver_id: props.uid, talk_type: 1 }).then((res) => {
|
||||||
|
// if (res?.code === 200) {
|
||||||
|
// showBtn.value = true
|
||||||
|
// isFriend.value = res.data?.is_friend || false
|
||||||
|
// }
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
const onAfterLeave = () => {
|
const onAfterLeave = () => {
|
||||||
// loading.value = true
|
// loading.value = true
|
||||||
@ -188,10 +216,18 @@ const onAfterLeave = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<x-n-modal content-style="padding:0;" :closable="false" class="w-311px min-h-445px" style="border-radius: 10px;overflow:hidden;" :show="show" :on-after-leave="onAfterLeave" :on-after-enter="onAfterEnter">
|
<x-n-modal
|
||||||
|
content-style="padding:0;"
|
||||||
|
:closable="false"
|
||||||
|
class="w-311px min-h-445px"
|
||||||
|
style="border-radius: 10px; overflow: hidden"
|
||||||
|
:show="show"
|
||||||
|
:on-after-leave="onAfterLeave"
|
||||||
|
:on-after-enter="onAfterEnter"
|
||||||
|
>
|
||||||
<div class="section relative px-7px pt-82px pb-20px">
|
<div class="section relative px-7px pt-82px pb-20px">
|
||||||
<div class="absolute top-9px right-7px pointer z-10" @click="emit('update:show', false)">
|
<div class="absolute top-9px right-7px pointer z-10" @click="emit('update:show', false)">
|
||||||
<img class="w-20px h-20px" src="@/assets/image/close.png" alt="">
|
<img class="w-20px h-20px" src="@/assets/image/close.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
@ -200,27 +236,25 @@ const onAfterLeave = () => {
|
|||||||
<n-skeleton height="59px" width="59px" />
|
<n-skeleton height="59px" width="59px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<n-skeleton text style="width: 80%; margin-bottom: 5px;" />
|
<n-skeleton text style="width: 80%; margin-bottom: 5px" />
|
||||||
<n-skeleton text style="width: 60%;" />
|
<n-skeleton text style="width: 60%" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-#fff rounded-4px mb-20px">
|
<div class="bg-#fff rounded-4px mb-20px">
|
||||||
<div class="flex px-15px py-9px" v-for="i in 6" :key="i">
|
<div class="flex px-15px py-9px" v-for="i in 6" :key="i">
|
||||||
<n-skeleton text style="width: 30%; margin-right: 10px;" />
|
<n-skeleton text style="width: 30%; margin-right: 10px" />
|
||||||
<n-skeleton text style="width: 60%;" />
|
<n-skeleton text style="width: 60%" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<n-skeleton text style="width: 100%; height: 42px; border-radius: 4px;" />
|
<n-skeleton text style="width: 100%; height: 42px; border-radius: 4px" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="flex py-10px bg-#fff px-16px rounded-4px items-center mb-10px">
|
<div class="flex py-10px bg-#fff px-16px rounded-4px items-center mb-10px">
|
||||||
<div class="w-59px h-59px rounded-8px mr-12px overflow-hidden">
|
<div class="w-59px h-59px rounded-8px mr-12px overflow-hidden">
|
||||||
<n-image width="59" :src="userInfo.avatar" >
|
<n-image width="59" :src="userInfo.avatar"> </n-image>
|
||||||
|
|
||||||
</n-image>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-#000 text-16px mb-5px">{{ userInfo.nickname }}</div>
|
<div class="text-#000 text-16px mb-5px">{{ userInfo.nickname }}</div>
|
||||||
@ -234,11 +268,15 @@ const onAfterLeave = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex px-15px py-9px">
|
<div class="flex px-15px py-9px">
|
||||||
<div class="text-#000 text-12px w-84px">主管</div>
|
<div class="text-#000 text-12px w-84px">主管</div>
|
||||||
<div class="text-#747474 text-12px">{{ userInfo.leaders?.map(x=>x.user_name)?.join(',') }}</div>
|
<div class="text-#747474 text-12px">
|
||||||
|
{{ userInfo.leaders?.map((x) => x.user_name)?.join(',') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex px-15px py-9px">
|
<div class="flex px-15px py-9px">
|
||||||
<div class="text-#000 text-12px w-84px">部门</div>
|
<div class="text-#000 text-12px w-84px">部门</div>
|
||||||
<div class="text-#747474 text-12px">{{ userInfo.erp_dept_position?.map(x=>x.department_name)?.join(',') }}</div>
|
<div class="text-#747474 text-12px">
|
||||||
|
{{ userInfo.erp_dept_position?.map((x) => x.department_name)?.join(',') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex px-15px py-9px">
|
<div class="flex px-15px py-9px">
|
||||||
<div class="text-#000 text-12px w-84px">手机号</div>
|
<div class="text-#000 text-12px w-84px">手机号</div>
|
||||||
@ -246,21 +284,48 @@ const onAfterLeave = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex px-15px py-9px">
|
<div class="flex px-15px py-9px">
|
||||||
<div class="text-#000 text-12px w-84px">岗位</div>
|
<div class="text-#000 text-12px w-84px">岗位</div>
|
||||||
<div class="text-#747474 text-12px">{{ userInfo.erp_dept_position?.map(x=>x.position_name)?.join(',') }}</div>
|
<div class="text-#747474 text-12px">
|
||||||
|
{{ userInfo.erp_dept_position?.map((x) => x.position_name)?.join(',') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex px-15px py-9px">
|
<div class="flex px-15px py-9px">
|
||||||
<div class="text-#000 text-12px w-84px">入职日期</div>
|
<div class="text-#000 text-12px w-84px">入职日期</div>
|
||||||
<div class="text-#747474 text-12px">{{ userInfo.enter_date }}</div>
|
<div class="text-#747474 text-12px">{{ userInfo.enter_date }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<n-button block color="#EEE9F8" text-color="#46299D" @click="onToTalk">
|
<n-button block color="#EEE9F8" text-color="#46299D" @click="onToTalk">
|
||||||
<div class="flex items-center justify-center py-11px">
|
<div class="flex items-center justify-center py-11px">
|
||||||
<img class="w-19.8px h-20px mr-15px" src="@/assets/image/faxi@2x.png" alt="">
|
<img class="w-19.8px h-20px mr-15px" src="@/assets/image/faxi@2x.png" alt="" />
|
||||||
<span>发送消息</span>
|
<span>发送消息</span>
|
||||||
</div>
|
</div>
|
||||||
</n-button>
|
</n-button>
|
||||||
|
<!-- <div v-if="showBtn">
|
||||||
|
<n-button block color="#EEE9F8" text-color="#46299D" @click="onToTalk" v-if="isFriend">
|
||||||
|
<div class="flex items-center justify-center py-11px">
|
||||||
|
<img class="w-19.8px h-20px mr-15px" src="@/assets/image/faxi@2x.png" alt="" />
|
||||||
|
<span>发送消息</span>
|
||||||
</div>
|
</div>
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
block
|
||||||
|
type="success"
|
||||||
|
color="#46299D"
|
||||||
|
text-color="#ffffff"
|
||||||
|
@click="addFriend"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-center py-11px">
|
||||||
|
<img
|
||||||
|
class="w-10px h-10px mr-15px"
|
||||||
|
src="@/assets/image/icon/close-btn-grey-line.png"
|
||||||
|
alt=""
|
||||||
|
style="transform: rotate(45deg)"
|
||||||
|
/>
|
||||||
|
<span>添加好友</span>
|
||||||
|
</div>
|
||||||
|
</n-button>
|
||||||
|
</div> -->
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</x-n-modal>
|
</x-n-modal>
|
||||||
|
@ -96,6 +96,7 @@ export const MessageComponents = {
|
|||||||
|
|
||||||
// 可转发的消息类型
|
// 可转发的消息类型
|
||||||
export const ForwardableMessageType = [
|
export const ForwardableMessageType = [
|
||||||
|
ChatMsgTypeForward,
|
||||||
ChatMsgTypeText,
|
ChatMsgTypeText,
|
||||||
ChatMsgTypeCode,
|
ChatMsgTypeCode,
|
||||||
ChatMsgTypeImage,
|
ChatMsgTypeImage,
|
||||||
|
@ -32,7 +32,7 @@ class Read extends Base {
|
|||||||
|
|
||||||
handle() {
|
handle() {
|
||||||
if (this.type == 'total') {
|
if (this.type == 'total') {
|
||||||
console.error('====接收到了新版已读回执全量=====', this.resource)
|
|
||||||
const readList = this.resource.result
|
const readList = this.resource.result
|
||||||
if (readList.length > 0) {
|
if (readList.length > 0) {
|
||||||
readList.forEach((item) => {
|
readList.forEach((item) => {
|
||||||
|
@ -240,14 +240,13 @@ class Talk extends Base {
|
|||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
console.log('输出加载1')
|
||||||
// 获取聊天面板元素节点
|
// 获取聊天面板元素节点
|
||||||
const el = document.getElementById('imChatPanel')
|
const el = document.getElementById('imChatPanel')
|
||||||
if (!el) return
|
if (!el) return
|
||||||
|
|
||||||
// 判断的滚动条是否在底部
|
// 判断的滚动条是否在底部
|
||||||
const isBottom = isScrollAtBottom(el)
|
const isBottom = isScrollAtBottom(el)
|
||||||
|
|
||||||
if (isBottom || record.user_id == this.getAccountId()) {
|
if (isBottom || record.user_id == this.getAccountId()) {
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
} else {
|
} else {
|
||||||
|
@ -38,23 +38,23 @@ export function useSessionMenu() {
|
|||||||
|
|
||||||
const options: any[] = []
|
const options: any[] = []
|
||||||
|
|
||||||
if (item.talk_type == 1) {
|
// if (item.talk_type == 1) {
|
||||||
options.push({
|
// options.push({
|
||||||
|
|
||||||
label: '好友信息',
|
// label: '好友信息',
|
||||||
key: 'info'
|
// key: 'info'
|
||||||
})
|
// })
|
||||||
|
|
||||||
|
// options.push({
|
||||||
|
|
||||||
|
// label: '修改备注',
|
||||||
|
// key: 'remark'
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
|
|
||||||
label: '修改备注',
|
label: item.is_top ? '取消置顶' : '置顶',
|
||||||
key: 'remark'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
options.push({
|
|
||||||
|
|
||||||
label: item.is_top ? '取消置顶' : '会话置顶',
|
|
||||||
key: 'top'
|
key: 'top'
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ export function useSessionMenu() {
|
|||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
|
|
||||||
label: '移除会话',
|
label: '删除聊天',
|
||||||
key: 'remove'
|
key: 'remove'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -130,19 +130,19 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
cursor: loadConfig.cursor,
|
cursor: loadConfig.cursor,
|
||||||
limit: 30
|
limit: 30
|
||||||
}
|
}
|
||||||
|
// 如果不是从本地数据库加载的,则设置加载状态为0(加载中)
|
||||||
|
if (loadConfig.status !== 2 && loadConfig.status !== 3) {
|
||||||
loadConfig.status = 0
|
loadConfig.status = 0
|
||||||
|
}
|
||||||
|
|
||||||
let scrollHeight = 0
|
let scrollHeight = 0
|
||||||
console.log('加载数据列表load')
|
|
||||||
const el = document.getElementById('imChatPanel')
|
const el = document.getElementById('imChatPanel')
|
||||||
if (el) {
|
if (el) {
|
||||||
scrollHeight = el.scrollHeight
|
scrollHeight = el.scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, code } = await ServeTalkRecords(request)
|
const { data, code } = await ServeTalkRecords(request)
|
||||||
if (code != 200) {
|
if (code != 200) {
|
||||||
return (loadConfig.status = 1)
|
return (loadConfig.status = (loadConfig.status === 2 || loadConfig.status === 3) ? loadConfig.status : 1) // 如果已经从本地加载了数据,保持原状态
|
||||||
}
|
}
|
||||||
// 防止对话切换过快,数据渲染错误
|
// 防止对话切换过快,数据渲染错误
|
||||||
if (
|
if (
|
||||||
@ -154,6 +154,64 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
|
|
||||||
const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
||||||
|
|
||||||
|
// 同步到本地数据库
|
||||||
|
try {
|
||||||
|
const { batchAddOrUpdateMessages } = await import('@/utils/db')
|
||||||
|
await batchAddOrUpdateMessages(data.items || [], params.talk_type, params.receiver_id, true, 'sequence')
|
||||||
|
console.log('聊天记录已同步到本地数据库')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('同步聊天记录到本地数据库失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是从本地数据库加载的数据,且服务器返回的数据与本地数据相同,则不需要更新UI
|
||||||
|
if ((loadConfig.status === 2 || loadConfig.status === 3) && request.cursor === 0) {
|
||||||
|
try {
|
||||||
|
// 获取最新的本地数据库消息进行比较
|
||||||
|
const { getMessages } = await import('@/utils/db')
|
||||||
|
const localMessages = await getMessages(
|
||||||
|
params.talk_type,
|
||||||
|
uid,
|
||||||
|
params.receiver_id,
|
||||||
|
items.length || 30, // 获取与服务器返回数量相同的消息
|
||||||
|
0 // 从第一页开始
|
||||||
|
)
|
||||||
|
|
||||||
|
// 格式化本地消息,确保与服务器消息结构一致
|
||||||
|
const formattedLocalMessages = localMessages.map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
||||||
|
|
||||||
|
|
||||||
|
// 改进比较逻辑:检查消息数量和所有消息的ID是否匹配
|
||||||
|
if (formattedLocalMessages.length === items.length && formattedLocalMessages.length > 0) {
|
||||||
|
// 创建消息ID映射,用于快速查找
|
||||||
|
const serverMsgMap = new Map()
|
||||||
|
items.forEach(item => serverMsgMap.set(item.msg_id, item))
|
||||||
|
|
||||||
|
// 检查每条本地消息是否与服务器消息匹配
|
||||||
|
const allMatch = formattedLocalMessages.every(localMsg => {
|
||||||
|
const serverMsg = serverMsgMap.get(localMsg.msg_id)
|
||||||
|
// 检查消息是否存在且关键状态是否一致(考虑撤回、已读等状态变化)
|
||||||
|
return serverMsg &&
|
||||||
|
serverMsg.is_revoke === localMsg.is_revoke &&
|
||||||
|
serverMsg.is_read === localMsg.is_read &&
|
||||||
|
(serverMsg.send_status === localMsg.send_status ||
|
||||||
|
(!serverMsg.send_status && !localMsg.send_status)) &&
|
||||||
|
serverMsg.content === localMsg.content
|
||||||
|
})
|
||||||
|
|
||||||
|
if (allMatch) {
|
||||||
|
console.log('本地数据与服务器数据一致,无需更新UI')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据不一致,需要更新UI
|
||||||
|
console.log('本地数据与服务器数据不一致,更新UI')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('比较本地数据和服务器数据时出错:', error)
|
||||||
|
// 出错时默认更新UI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (request.cursor == 0) {
|
if (request.cursor == 0) {
|
||||||
// 判断是否是初次加载
|
// 判断是否是初次加载
|
||||||
dialogueStore.clearDialogueRecord()
|
dialogueStore.clearDialogueRecord()
|
||||||
@ -167,7 +225,6 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const el = document.getElementById('imChatPanel')
|
const el = document.getElementById('imChatPanel')
|
||||||
|
|
||||||
if (el) {
|
if (el) {
|
||||||
if (request.cursor == 0) {
|
if (request.cursor == 0) {
|
||||||
// el.scrollTop = el.scrollHeight
|
// el.scrollTop = el.scrollHeight
|
||||||
@ -175,6 +232,12 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
// el.scrollTop = el.scrollHeight + 1000
|
// el.scrollTop = el.scrollHeight + 1000
|
||||||
// }, 500)
|
// }, 500)
|
||||||
|
console.log('滚动到底部')
|
||||||
|
|
||||||
|
// 在初次加载完成后恢复上传任务
|
||||||
|
// 确保在所有聊天记录加载完成后再恢复上传任务
|
||||||
|
dialogueStore.restoreUploadTasks()
|
||||||
|
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
} else {
|
} else {
|
||||||
el.scrollTop = el.scrollHeight - scrollHeight
|
el.scrollTop = el.scrollHeight - scrollHeight
|
||||||
@ -189,9 +252,7 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
|
|
||||||
// 获取当前消息的最小 sequence
|
// 获取当前消息的最小 sequence
|
||||||
const getMinSequence = () => {
|
const getMinSequence = () => {
|
||||||
console.error('records.value', records.value)
|
|
||||||
if (!records.value.length) return 0
|
if (!records.value.length) return 0
|
||||||
console.error(Math.min(...records.value.map((item) => item.sequence)))
|
|
||||||
return Math.min(...records.value.map((item) => item.sequence))
|
return Math.min(...records.value.map((item) => item.sequence))
|
||||||
}
|
}
|
||||||
// 获取当前消息的最大 sequence
|
// 获取当前消息的最大 sequence
|
||||||
@ -200,13 +261,56 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
return Math.max(...records.value.map((item) => item.sequence))
|
return Math.max(...records.value.map((item) => item.sequence))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从本地数据库加载聊天记录
|
||||||
|
const loadFromLocalDB = async (params: Params) => {
|
||||||
|
try {
|
||||||
|
// 导入 getMessages 函数
|
||||||
|
const { getMessages } = await import('@/utils/db')
|
||||||
|
// 从本地数据库获取聊天记录
|
||||||
|
const localMessages = await getMessages(
|
||||||
|
params.talk_type,
|
||||||
|
uid,
|
||||||
|
params.receiver_id,
|
||||||
|
params.limit || 30,
|
||||||
|
0 // 从第一页开始
|
||||||
|
// 不传入 maxSequence 参数,获取最新的消息
|
||||||
|
)
|
||||||
|
// 如果有本地数据
|
||||||
|
if (localMessages && localMessages.length > 0) {
|
||||||
|
// 清空现有记录
|
||||||
|
dialogueStore.clearDialogueRecord()
|
||||||
|
|
||||||
|
// 格式化并添加记录
|
||||||
|
const formattedMessages = localMessages.map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
||||||
|
dialogueStore.unshiftDialogueRecord(formattedMessages)
|
||||||
|
|
||||||
|
// 设置加载状态为完成(3表示从本地数据库加载完成)
|
||||||
|
loadConfig.status = 3
|
||||||
|
|
||||||
|
// 恢复上传任务
|
||||||
|
dialogueStore.restoreUploadTasks()
|
||||||
|
|
||||||
|
// 滚动到底部
|
||||||
|
nextTick(() => {
|
||||||
|
scrollToBottom()
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('从本地数据库加载聊天记录失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载数据主入口,支持指定消息定位模式
|
* 加载数据主入口,支持指定消息定位模式
|
||||||
* @param params 原有参数
|
* @param params 原有参数
|
||||||
* @param options 可选,{ specifiedMsg } 指定消息对象
|
* @param options 可选,{ specifiedMsg } 指定消息对象
|
||||||
*/
|
*/
|
||||||
const onLoad = (params: Params, options?: LoadOptions) => {
|
const onLoad = async (params: Params, options?: LoadOptions) => {
|
||||||
// 如果会话切换,重置所有状态
|
|
||||||
if (
|
if (
|
||||||
params.talk_type !== loadConfig.talk_type ||
|
params.talk_type !== loadConfig.talk_type ||
|
||||||
params.receiver_id !== loadConfig.receiver_id
|
params.receiver_id !== loadConfig.receiver_id
|
||||||
@ -221,7 +325,6 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
// 新增:支持指定消息定位模式,参数以传入为准合并
|
// 新增:支持指定消息定位模式,参数以传入为准合并
|
||||||
if (options?.specifiedMsg?.cursor !== undefined) {
|
if (options?.specifiedMsg?.cursor !== undefined) {
|
||||||
loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用
|
loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用
|
||||||
console.error('options', options)
|
|
||||||
loadConfig.status = 0 // 复用主流程 loading 状态
|
loadConfig.status = 0 // 复用主流程 loading 状态
|
||||||
// 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖)
|
// 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖)
|
||||||
const contextParams = {
|
const contextParams = {
|
||||||
@ -231,6 +334,7 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
//msg_id是用来做定位的,不做参数,所以这里清空
|
//msg_id是用来做定位的,不做参数,所以这里清空
|
||||||
contextParams.msg_id = ''
|
contextParams.msg_id = ''
|
||||||
ServeTalkRecords(contextParams).then(({ data, code }) => {
|
ServeTalkRecords(contextParams).then(({ data, code }) => {
|
||||||
|
console.log('data',data)
|
||||||
if (code !== 200) {
|
if (code !== 200) {
|
||||||
loadConfig.status = 2
|
loadConfig.status = 2
|
||||||
return
|
return
|
||||||
@ -322,6 +426,8 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 其他情况滚动到底部
|
// 其他情况滚动到底部
|
||||||
|
// 在特殊参数模式下也需要恢复上传任务
|
||||||
|
dialogueStore.restoreUploadTasks()
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,14 +437,22 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadConfig.specialParams = undefined // 普通模式清空
|
loadConfig.specialParams = undefined // 普通模式清空
|
||||||
|
|
||||||
|
// 设置初始加载状态为0(加载中)
|
||||||
|
loadConfig.status = 0
|
||||||
|
|
||||||
|
// 先从本地数据库加载数据
|
||||||
|
const hasLocalData = await loadFromLocalDB(params)
|
||||||
|
|
||||||
|
// 无论是否有本地数据,都从服务器获取最新数据
|
||||||
// 原有逻辑
|
// 原有逻辑
|
||||||
|
console.log('onLoad()执行load')
|
||||||
load(params)
|
load(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向上加载更多(兼容特殊参数模式)
|
// 向上加载更多(兼容特殊参数模式)
|
||||||
const onRefreshLoad = () => {
|
const onRefreshLoad = () => {
|
||||||
console.error('loadConfig.status', loadConfig.status)
|
if (loadConfig.status == 1 || loadConfig.status == 3) {
|
||||||
if (loadConfig.status == 1) {
|
|
||||||
console.log('specialParams', loadConfig.specialParams)
|
console.log('specialParams', loadConfig.specialParams)
|
||||||
// 判断是否是特殊参数模式
|
// 判断是否是特殊参数模式
|
||||||
if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') {
|
if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') {
|
||||||
@ -369,6 +483,7 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
} else {
|
} else {
|
||||||
// 如果不匹配,重置为普通模式
|
// 如果不匹配,重置为普通模式
|
||||||
resetLoadConfig()
|
resetLoadConfig()
|
||||||
|
console.log('load执行2')
|
||||||
load({
|
load({
|
||||||
receiver_id: loadConfig.receiver_id,
|
receiver_id: loadConfig.receiver_id,
|
||||||
talk_type: loadConfig.talk_type,
|
talk_type: loadConfig.talk_type,
|
||||||
@ -377,6 +492,7 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 原有逻辑
|
// 原有逻辑
|
||||||
|
console.log('load执行3')
|
||||||
load({
|
load({
|
||||||
receiver_id: loadConfig.receiver_id,
|
receiver_id: loadConfig.receiver_id,
|
||||||
talk_type: loadConfig.talk_type,
|
talk_type: loadConfig.talk_type,
|
||||||
|
@ -31,14 +31,14 @@ function handle() {
|
|||||||
|
|
||||||
once = true
|
once = true
|
||||||
|
|
||||||
window['$dialog'].info({
|
// window['$dialog'].info({
|
||||||
title: '友情提示',
|
// title: '友情提示',
|
||||||
content: '当前登录已失效,请重新登录?',
|
// content: '当前登录已失效,请重新登录?',
|
||||||
positiveText: '立即登录?',
|
// positiveText: '立即登录?',
|
||||||
maskClosable: false,
|
// maskClosable: false,
|
||||||
onPositiveClick: () => {
|
// onPositiveClick: () => {
|
||||||
once = false
|
// once = false
|
||||||
useRouter().push('/auth/login')
|
// useRouter().push('/auth/login')
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,13 @@ import router from './router'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import * as plugins from './plugins'
|
import * as plugins from './plugins'
|
||||||
import request from "@/api/index.js";
|
import request from "@/api/index.js";
|
||||||
|
|
||||||
if (window.__POWERED_BY_WUJIE__) {
|
if (window.__POWERED_BY_WUJIE__) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
window.__webpack_public_path__ = window.__WUJIE_PUBLIC_PATH__;
|
window.__webpack_public_path__ = window.__WUJIE_PUBLIC_PATH__;
|
||||||
}
|
}
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
@ -15,7 +15,9 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
return {
|
return {
|
||||||
// 对话索引(聊天对话的唯一索引)
|
// 对话索引(聊天对话的唯一索引)
|
||||||
index_name: '',
|
index_name: '',
|
||||||
|
globalUploadList:[],
|
||||||
|
// 添加一个映射,用于快速查找每个会话的上传任务
|
||||||
|
uploadTaskMap: {}, // 格式: { "talk_type_receiver_id": [task1, task2, ...] }
|
||||||
// 对话节点
|
// 对话节点
|
||||||
talk: {
|
talk: {
|
||||||
avatar:'',
|
avatar:'',
|
||||||
@ -129,8 +131,10 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
if (data.talk_type == 2) {
|
if (data.talk_type == 2) {
|
||||||
this.updateGroupMembers()
|
this.updateGroupMembers()
|
||||||
this.getGroupInfo()
|
this.getGroupInfo()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注意:上传任务的恢复将在聊天记录加载完成后进行
|
||||||
|
// 在useTalkRecord.ts的onLoad方法中,会在加载完聊天记录后调用restoreUploadTasks方法
|
||||||
},
|
},
|
||||||
|
|
||||||
// 更新提及列表
|
// 更新提及列表
|
||||||
@ -171,10 +175,12 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
|
|
||||||
// 数组头部压入对话记录
|
// 数组头部压入对话记录
|
||||||
unshiftDialogueRecord(records) {
|
unshiftDialogueRecord(records) {
|
||||||
|
console.log('unshiftDialogueRecord')
|
||||||
this.records.unshift(...records)
|
this.records.unshift(...records)
|
||||||
},
|
},
|
||||||
//数组尾部加入更多对话记录
|
//数组尾部加入更多对话记录
|
||||||
addDialogueRecordForLoadMore(records){
|
addDialogueRecordForLoadMore(records){
|
||||||
|
console.log('addDialogueRecordForLoadMore')
|
||||||
this.records.push(...records)
|
this.records.push(...records)
|
||||||
},
|
},
|
||||||
async getGroupInfo(){
|
async getGroupInfo(){
|
||||||
@ -186,24 +192,55 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 推送对话记录
|
// 推送对话记录
|
||||||
addDialogueRecord(record) {
|
async addDialogueRecord(record) {
|
||||||
// TOOD 需要通过 sequence 排序,保证消息一致性
|
// TOOD 需要通过 sequence 排序,保证消息一致性
|
||||||
// this.records.splice(index, 0, record)
|
// this.records.splice(index, 0, record)
|
||||||
|
|
||||||
this.records.push(record)
|
this.records.push(record)
|
||||||
|
|
||||||
|
// 同步到本地数据库
|
||||||
|
try {
|
||||||
|
const { addMessage } = await import('@/utils/db')
|
||||||
|
await addMessage(record)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('同步消息到本地数据库失败:', error)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 更新对话记录
|
// 更新对话记录
|
||||||
updateDialogueRecord(params) {
|
async updateDialogueRecord(params) {
|
||||||
const { msg_id = '' } = params
|
const { msg_id = '' } = params
|
||||||
|
|
||||||
const item = this.records.find((item) => item.msg_id === msg_id)
|
const item = this.records.find((item) => item.msg_id === msg_id)
|
||||||
|
|
||||||
item && Object.assign(item, params)
|
if (item) {
|
||||||
|
Object.assign(item, params)
|
||||||
|
|
||||||
|
// 同步到本地数据库
|
||||||
|
try {
|
||||||
|
// 如果是撤回消息
|
||||||
|
if (params.is_revoke === 1) {
|
||||||
|
const { revokeMessage } = await import('@/utils/db')
|
||||||
|
await revokeMessage(msg_id)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('同步消息更新到本地数据库失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 批量删除对话记录
|
// 批量删除对话记录
|
||||||
batchDelDialogueRecord(msgIds = []) {
|
async batchDelDialogueRecord(msgIds = []) {
|
||||||
|
// 同步到本地数据库
|
||||||
|
try {
|
||||||
|
const { deleteMessage } = await import('@/utils/db')
|
||||||
|
for (const msgid of msgIds) {
|
||||||
|
await deleteMessage(msgid)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('同步消息删除到本地数据库失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从内存中删除
|
||||||
msgIds.forEach((msgid) => {
|
msgIds.forEach((msgid) => {
|
||||||
const index = this.records.findIndex((item) => item.msg_id === msgid)
|
const index = this.records.findIndex((item) => item.msg_id === msgid)
|
||||||
|
|
||||||
@ -248,8 +285,6 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
this.batchDelDialogueRecord(msgIds)
|
this.batchDelDialogueRecord(msgIds)
|
||||||
} else {
|
|
||||||
window['$message'].warning(res.message)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -294,6 +329,16 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
|
|
||||||
// 更新视频上传进度
|
// 更新视频上传进度
|
||||||
updateUploadProgress(uploadId, percentage) {
|
updateUploadProgress(uploadId, percentage) {
|
||||||
|
// 更新全局列表中的进度
|
||||||
|
const globalTask = this.globalUploadList.find(item =>
|
||||||
|
item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (globalTask) {
|
||||||
|
globalTask.extra.percentage = percentage
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新当前会话记录中的进度
|
||||||
const record = this.records.find(item =>
|
const record = this.records.find(item =>
|
||||||
item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId
|
item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId
|
||||||
)
|
)
|
||||||
@ -303,6 +348,44 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 添加上传任务
|
||||||
|
addUploadTask(task) {
|
||||||
|
// 添加到全局列表
|
||||||
|
this.globalUploadList.push(task)
|
||||||
|
|
||||||
|
// 添加到会话映射
|
||||||
|
const sessionKey = `${task.talk_type}_${task.receiver_id}`
|
||||||
|
if (!this.uploadTaskMap[sessionKey]) {
|
||||||
|
this.uploadTaskMap[sessionKey] = []
|
||||||
|
}
|
||||||
|
this.uploadTaskMap[sessionKey].push(task)
|
||||||
|
|
||||||
|
// 同时添加到当前会话记录
|
||||||
|
this.addDialogueRecord(task)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传完成后移除任务
|
||||||
|
removeUploadTask(uploadId) {
|
||||||
|
// 从全局列表中找到任务
|
||||||
|
const taskIndex = this.globalUploadList.findIndex(item => item.msg_id === uploadId)
|
||||||
|
|
||||||
|
if (taskIndex >= 0) {
|
||||||
|
const task = this.globalUploadList[taskIndex]
|
||||||
|
const sessionKey = `${task.talk_type}_${task.receiver_id}`
|
||||||
|
|
||||||
|
// 从会话映射中移除
|
||||||
|
if (this.uploadTaskMap[sessionKey]) {
|
||||||
|
const mapIndex = this.uploadTaskMap[sessionKey].findIndex(item => item.msg_id === uploadId)
|
||||||
|
if (mapIndex >= 0) {
|
||||||
|
this.uploadTaskMap[sessionKey].splice(mapIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从全局列表中移除
|
||||||
|
this.globalUploadList.splice(taskIndex, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 视频上传完成后更新消息
|
// 视频上传完成后更新消息
|
||||||
completeUpload(uploadId, videoInfo) {
|
completeUpload(uploadId, videoInfo) {
|
||||||
const record = this.records.find(item =>
|
const record = this.records.find(item =>
|
||||||
@ -319,6 +402,135 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
// 更新会话信息
|
// 更新会话信息
|
||||||
updateDialogueTalk(params){
|
updateDialogueTalk(params){
|
||||||
Object.assign(this.talk, params)
|
Object.assign(this.talk, params)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 根据 insert_sequence 将任务插入到 records 数组的正确位置(使用优化的二分查找)
|
||||||
|
insertTaskAtCorrectPosition(task) {
|
||||||
|
const len = this.records.length
|
||||||
|
|
||||||
|
// 快速路径:如果数组为空或任务应该插入到末尾
|
||||||
|
if (len === 0) {
|
||||||
|
this.records.push(task)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快速路径:检查是否应该插入到开头或末尾(避免二分查找的开销)
|
||||||
|
if (task.insert_sequence < this.records[0].sequence) {
|
||||||
|
this.records.unshift(task)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.insert_sequence >= this.records[len - 1].sequence) {
|
||||||
|
this.records.push(task)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用优化的二分查找算法找到插入位置
|
||||||
|
let low = 0
|
||||||
|
let high = len - 1
|
||||||
|
|
||||||
|
// 二分查找优化:使用位运算加速计算中点
|
||||||
|
while (low <= high) {
|
||||||
|
const mid = (low + high) >>> 1 // 无符号右移代替 Math.floor((low + high) / 2)
|
||||||
|
if (this.records[mid].sequence <= task.insert_sequence) {
|
||||||
|
low = mid + 1
|
||||||
|
} else {
|
||||||
|
high = mid - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在找到的位置插入任务
|
||||||
|
this.records.splice(low, 0, task)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 恢复当前会话的上传任务
|
||||||
|
restoreUploadTasks() {
|
||||||
|
// 获取当前会话的sessionKey
|
||||||
|
const sessionKey = `${this.talk.talk_type}_${this.talk.receiver_id}`
|
||||||
|
|
||||||
|
// 检查是否有需要恢复的上传任务
|
||||||
|
if (!this.uploadTaskMap[sessionKey] || this.uploadTaskMap[sessionKey].length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能优化:缓存数组长度和本地变量,减少属性查找
|
||||||
|
const tasks = this.uploadTaskMap[sessionKey]
|
||||||
|
const tasksLength = tasks.length
|
||||||
|
|
||||||
|
// 如果只有一个任务,直接处理
|
||||||
|
if (tasksLength === 1) {
|
||||||
|
this.insertTaskAtCorrectPosition(tasks[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能优化:对于少量任务,避免创建新数组和排序开销
|
||||||
|
if (tasksLength <= 10) {
|
||||||
|
// 找出最小的 insert_sequence
|
||||||
|
let minIndex = 0
|
||||||
|
for (let i = 1; i < tasksLength; i++) {
|
||||||
|
if (tasks[i].insert_sequence < tasks[minIndex].insert_sequence) {
|
||||||
|
minIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按顺序插入任务
|
||||||
|
let inserted = 0
|
||||||
|
let currentMin = tasks[minIndex]
|
||||||
|
this.insertTaskAtCorrectPosition(currentMin)
|
||||||
|
inserted++
|
||||||
|
|
||||||
|
while (inserted < tasksLength) {
|
||||||
|
minIndex = -1
|
||||||
|
let minSequence = Infinity
|
||||||
|
|
||||||
|
// 找出剩余任务中 insert_sequence 最小的
|
||||||
|
for (let i = 0; i < tasksLength; i++) {
|
||||||
|
const task = tasks[i]
|
||||||
|
if (task !== currentMin && task.insert_sequence < minSequence) {
|
||||||
|
minIndex = i
|
||||||
|
minSequence = task.insert_sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minIndex !== -1) {
|
||||||
|
currentMin = tasks[minIndex]
|
||||||
|
this.insertTaskAtCorrectPosition(currentMin)
|
||||||
|
inserted++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 对于大量任务,使用排序后批量处理
|
||||||
|
// 创建一个新数组并排序,避免修改原数组
|
||||||
|
const sortedTasks = [...tasks].sort((a, b) => a.insert_sequence - b.insert_sequence)
|
||||||
|
|
||||||
|
// 性能优化:使用 requestAnimationFrame 进行批处理,更好地配合浏览器渲染周期
|
||||||
|
const batchSize = 50 // 每批处理的任务数量
|
||||||
|
const totalBatches = Math.ceil(sortedTasks.length / batchSize)
|
||||||
|
|
||||||
|
const processBatch = (batchIndex) => {
|
||||||
|
const startIndex = batchIndex * batchSize
|
||||||
|
const endIndex = Math.min(startIndex + batchSize, sortedTasks.length)
|
||||||
|
|
||||||
|
// 处理当前批次的任务
|
||||||
|
for (let i = startIndex; i < endIndex; i++) {
|
||||||
|
this.insertTaskAtCorrectPosition(sortedTasks[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果还有更多批次,安排下一个批次
|
||||||
|
if (batchIndex < totalBatches - 1) {
|
||||||
|
// 使用 requestAnimationFrame 配合浏览器渲染周期
|
||||||
|
// 如果不支持,回退到 setTimeout
|
||||||
|
if (typeof requestAnimationFrame !== 'undefined') {
|
||||||
|
requestAnimationFrame(() => processBatch(batchIndex + 1))
|
||||||
|
} else {
|
||||||
|
setTimeout(() => processBatch(batchIndex + 1), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始处理第一批
|
||||||
|
processBatch(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -3,6 +3,7 @@ import { ServeGetTalkList, ServeCreateTalkList } from '@/api/chat'
|
|||||||
import { formatTalkItem, ttime, KEY_INDEX_NAME } from '@/utils/talk'
|
import { formatTalkItem, ttime, KEY_INDEX_NAME } from '@/utils/talk'
|
||||||
import { useEditorDraftStore } from './editor-draft'
|
import { useEditorDraftStore } from './editor-draft'
|
||||||
import { ISession } from '@/types/chat'
|
import { ISession } from '@/types/chat'
|
||||||
|
import { getConversations, addOrUpdateConversation, deleteConversation, getConversation } from '@/utils/db'
|
||||||
|
|
||||||
interface TalkStoreState {
|
interface TalkStoreState {
|
||||||
loadStatus: number
|
loadStatus: number
|
||||||
@ -45,56 +46,103 @@ export const useTalkStore = defineStore('talk', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 更新对话节点
|
// 更新对话节点
|
||||||
updateItem(params: any) {
|
async updateItem(params: any) {
|
||||||
const item = this.items.find((item) => item.index_name === params.index_name)
|
const item = this.items.find((item) => item.index_name === params.index_name)
|
||||||
|
|
||||||
item && Object.assign(item, params)
|
if (item) {
|
||||||
|
Object.assign(item, params)
|
||||||
|
|
||||||
|
// 同步更新本地数据库
|
||||||
|
try {
|
||||||
|
await addOrUpdateConversation(JSON.parse(JSON.stringify(item)))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新本地会话失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 新增对话节点
|
// 新增对话节点
|
||||||
addItem(params: any) {
|
async addItem(params: any) {
|
||||||
this.items = [params, ...this.items]
|
this.items = [params, ...this.items]
|
||||||
|
|
||||||
|
// 同步添加到本地数据库
|
||||||
|
try {
|
||||||
|
await addOrUpdateConversation(JSON.parse(JSON.stringify(params)))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加本地会话失败:', error)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 移除对话节点
|
// 移除对话节点
|
||||||
delItem(index_name: string) {
|
async delItem(index_name: string) {
|
||||||
const i = this.items.findIndex((item) => item.index_name === index_name)
|
const i = this.items.findIndex((item) => item.index_name === index_name)
|
||||||
|
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
|
const item = this.items[i]
|
||||||
this.items.splice(i, 1)
|
this.items.splice(i, 1)
|
||||||
|
|
||||||
|
// 同步从本地数据库删除
|
||||||
|
try {
|
||||||
|
// 从本地数据库中查找并删除会话
|
||||||
|
const [talkType, receiverId] = index_name.split('_')
|
||||||
|
const conversation = await getConversation(Number(talkType), Number(receiverId))
|
||||||
|
|
||||||
|
if (conversation && conversation.id) {
|
||||||
|
await deleteConversation(conversation.id, false) // 不删除相关消息
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除本地会话失败:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.items = [...this.items]
|
this.items = [...this.items]
|
||||||
},
|
},
|
||||||
|
|
||||||
// 更新对话消息
|
// 更新对话消息
|
||||||
updateMessage(params: any) {
|
async updateMessage(params: any) {
|
||||||
const item = this.items.find((item) => item.index_name === params.index_name)
|
const item = this.items.find((item) => item.index_name === params.index_name)
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
item.unread_num++
|
item.unread_num++
|
||||||
item.msg_text = params.msg_text
|
item.msg_text = params.msg_text
|
||||||
item.updated_at = params.updated_at
|
item.updated_at = params.updated_at
|
||||||
|
|
||||||
|
// 同步更新本地数据库中的会话信息
|
||||||
|
try {
|
||||||
|
await addOrUpdateConversation(JSON.parse(JSON.stringify(item)))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新本地会话消息失败:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 更新联系人备注
|
// 更新联系人备注
|
||||||
setRemark(params: any) {
|
async setRemark(params: any) {
|
||||||
const item = this.items.find((item) => item.index_name === `1_${params.user_id}`)
|
const item = this.items.find((item) => item.index_name === `1_${params.user_id}`)
|
||||||
|
|
||||||
item && (item.remark = params.remark)
|
if (item) {
|
||||||
|
item.remark = params.remark
|
||||||
|
|
||||||
|
// 同步更新本地数据库
|
||||||
|
try {
|
||||||
|
await addOrUpdateConversation(JSON.parse(JSON.stringify(item)))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新本地联系人备注失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 加载会话列表
|
// 加载会话列表
|
||||||
loadTalkList() {
|
async loadTalkList() {
|
||||||
this.loadStatus = 2
|
this.loadStatus = 2
|
||||||
|
|
||||||
const resp = ServeGetTalkList()
|
try {
|
||||||
|
// 先从本地数据库加载会话列表
|
||||||
resp.then(({ code, data }) => {
|
const localConversations = await getConversations()
|
||||||
if (code == 200) {
|
if (localConversations && localConversations.length > 0) {
|
||||||
|
// 将本地会话列表转换为应用所需格式
|
||||||
this.items = data.items.map((item: any) => {
|
this.items = localConversations.map((item: any) => {
|
||||||
|
// 确保本地存储的会话格式与应用一致
|
||||||
const value = formatTalkItem(item)
|
const value = formatTalkItem(item)
|
||||||
|
|
||||||
const draft = useEditorDraftStore().items[value.index_name]
|
const draft = useEditorDraftStore().items[value.index_name]
|
||||||
@ -108,22 +156,63 @@ export const useTalkStore = defineStore('talk', {
|
|||||||
return value
|
return value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 设置为加载完成状态,因为已从本地加载了数据,不需要等待服务器数据就可以显示
|
||||||
this.loadStatus = 3
|
this.loadStatus = 3
|
||||||
} else {
|
|
||||||
this.loadStatus = 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从服务器获取最新会话列表
|
||||||
|
const resp = await ServeGetTalkList()
|
||||||
|
|
||||||
|
if (resp.code == 200) {
|
||||||
|
// 将服务器返回的会话列表转换为应用所需格式
|
||||||
|
const serverItems = resp.data.items.map((item: any) => {
|
||||||
|
const value = formatTalkItem(item)
|
||||||
|
|
||||||
|
const draft = useEditorDraftStore().items[value.index_name]
|
||||||
|
if (draft) {
|
||||||
|
value.draft_text = JSON.parse(draft).text || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.is_robot == 1) {
|
||||||
|
value.is_online = 1
|
||||||
|
}
|
||||||
|
return value
|
||||||
})
|
})
|
||||||
|
|
||||||
resp.catch(() => {
|
// 更新状态和本地数据库
|
||||||
|
this.items = serverItems
|
||||||
|
|
||||||
|
// 将最新的会话列表保存到本地数据库
|
||||||
|
for (const item of serverItems) {
|
||||||
|
await addOrUpdateConversation(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadStatus = 3
|
||||||
|
} else {
|
||||||
|
// 如果服务器请求失败但本地有数据,保持使用本地数据
|
||||||
|
if (this.items.length === 0) {
|
||||||
this.loadStatus = 4
|
this.loadStatus = 4
|
||||||
})
|
} else {
|
||||||
|
this.loadStatus = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载会话列表失败:', error)
|
||||||
|
|
||||||
|
// 如果有本地数据,即使服务器请求失败也显示本地数据
|
||||||
|
if (this.items.length === 0) {
|
||||||
|
this.loadStatus = 4
|
||||||
|
} else {
|
||||||
|
this.loadStatus = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
findTalkIndex(index_name: string) {
|
findTalkIndex(index_name: string) {
|
||||||
return this.items.findIndex((item: ISession) => item.index_name === index_name)
|
return this.items.findIndex((item: ISession) => item.index_name === index_name)
|
||||||
},
|
},
|
||||||
|
|
||||||
toTalk(talk_type: number, receiver_id: number, router: any) {
|
async toTalk(talk_type: number, receiver_id: number, router: any) {
|
||||||
const route = {
|
const route = {
|
||||||
path: '/message',
|
path: '/message',
|
||||||
query: {
|
query: {
|
||||||
@ -136,13 +225,31 @@ export const useTalkStore = defineStore('talk', {
|
|||||||
return router.push(route)
|
return router.push(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
ServeCreateTalkList({
|
try {
|
||||||
|
// 先检查本地数据库中是否有该会话
|
||||||
|
const localConversation = await getConversation(talk_type, receiver_id)
|
||||||
|
|
||||||
|
if (localConversation) {
|
||||||
|
// 如果本地有该会话,直接添加到列表中
|
||||||
|
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
|
||||||
|
this.addItem(formatTalkItem(localConversation))
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
|
||||||
|
return router.push(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果本地没有,则从服务器创建
|
||||||
|
const { code, data, message } = await ServeCreateTalkList({
|
||||||
talk_type,
|
talk_type,
|
||||||
receiver_id
|
receiver_id
|
||||||
}).then(({ code, data, message }) => {
|
})
|
||||||
|
|
||||||
if (code == 200) {
|
if (code == 200) {
|
||||||
|
const formattedItem = formatTalkItem(data)
|
||||||
|
|
||||||
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
|
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
|
||||||
this.addItem(formatTalkItem(data))
|
await this.addItem(formattedItem) // 使用 await 确保本地数据库同步更新
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
|
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
|
||||||
@ -150,7 +257,10 @@ export const useTalkStore = defineStore('talk', {
|
|||||||
} else {
|
} else {
|
||||||
window['$message'].info(message)
|
window['$message'].info(message)
|
||||||
}
|
}
|
||||||
})
|
} catch (error) {
|
||||||
|
console.error('创建会话失败:', error)
|
||||||
|
window['$message'].error('创建会话失败,请稍后再试')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ServeFindFileSplitInfo, ServeFileSubareaUpload } from '@/api/upload'
|
// import { message } from 'naive-ui'
|
||||||
import { ServeSendTalkFile } from '@/api/chat'
|
import {
|
||||||
import { uploadImg } from '@/api/upload'
|
ServeSendTalkFile
|
||||||
|
} from '@/api/chat'
|
||||||
|
import {
|
||||||
|
uploadImg,
|
||||||
|
ServeFindFileSplitInfo,
|
||||||
|
ServeFileSubareaUpload
|
||||||
|
} from '@/api/upload'
|
||||||
import {
|
import {
|
||||||
useDialogueStore
|
useDialogueStore
|
||||||
} from '@/store'
|
} from '@/store'
|
||||||
@ -140,12 +146,12 @@ export const useUploadsStore = defineStore('uploads', {
|
|||||||
this.triggerUpload(upload_id, clientUploadId)
|
this.triggerUpload(upload_id, clientUploadId)
|
||||||
} else {
|
} else {
|
||||||
message.error(res.message)
|
message.error(res.message)
|
||||||
onProgress(-1) // 通知上传失败
|
this.handleUploadError(upload_id, clientUploadId)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("初始化分片上传失败:", error);
|
console.error("初始化分片上传失败:", error);
|
||||||
message.error("初始化上传失败,请重试")
|
message.error("初始化上传失败,请重试")
|
||||||
onProgress(-1)
|
this.handleUploadError(upload_id, clientUploadId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -170,14 +176,14 @@ export const useUploadsStore = defineStore('uploads', {
|
|||||||
|
|
||||||
// 更新状态为上传中
|
// 更新状态为上传中
|
||||||
currentItem.status = 1
|
currentItem.status = 1
|
||||||
|
const updatedItem:any = this.findItem(uploadId)
|
||||||
// 上传当前分片
|
// 上传当前分片
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const res = await ServeFileSubareaUpload(form)
|
const res = await ServeFileSubareaUpload(form)
|
||||||
|
|
||||||
// 获取最新的项目状态,确保仍然存在且没有被暂停
|
// 获取最新的项目状态,确保仍然存在且没有被暂停
|
||||||
const updatedItem:any = this.findItem(uploadId)
|
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
// 当前分片上传成功,增加索引
|
// 当前分片上传成功,增加索引
|
||||||
updatedItem.uploadIndex++
|
updatedItem.uploadIndex++
|
||||||
@ -201,24 +207,20 @@ export const useUploadsStore = defineStore('uploads', {
|
|||||||
this.triggerUpload(uploadId, clientUploadId)
|
this.triggerUpload(uploadId, clientUploadId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updatedItem.onProgress(-1)
|
|
||||||
// 上传失败处理
|
// 上传失败处理
|
||||||
console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`);
|
console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`);
|
||||||
updatedItem.status = 3
|
this.handleUploadError(uploadId, clientUploadId || '')
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("分片上传错误:", error);
|
console.error("分片上传错误:", error);
|
||||||
|
|
||||||
// 获取最新的项目状态
|
// 获取最新的项目状态
|
||||||
const updatedItem = this.findItem(uploadId)
|
|
||||||
if (!updatedItem) return
|
if (!updatedItem) return
|
||||||
|
|
||||||
// 如果是暂停导致的错误,不改变状态
|
// 如果是暂停导致的错误,不改变状态
|
||||||
if (updatedItem.is_paused) return
|
if (updatedItem.is_paused) return
|
||||||
|
|
||||||
updatedItem.status = 3
|
this.handleUploadError(uploadId, clientUploadId || '')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -242,6 +244,10 @@ export const useUploadsStore = defineStore('uploads', {
|
|||||||
talk_type: item.talk_type
|
talk_type: item.talk_type
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 从DialogueStore中移除上传任务
|
||||||
|
const dialogueStore = useDialogueStore()
|
||||||
|
dialogueStore.removeUploadTask(clientUploadId)
|
||||||
|
|
||||||
if (item.onComplete) {
|
if (item.onComplete) {
|
||||||
item.onComplete(item)
|
item.onComplete(item)
|
||||||
}
|
}
|
||||||
@ -289,5 +295,21 @@ export const useUploadsStore = defineStore('uploads', {
|
|||||||
// 从上传列表中移除旧的上传项
|
// 从上传列表中移除旧的上传项
|
||||||
this.items = this.items.filter(i => i.client_upload_id !== clientUploadId)
|
this.items = this.items.filter(i => i.client_upload_id !== clientUploadId)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 上传失败处理
|
||||||
|
async handleUploadError(uploadId: string, clientUploadId: string) {
|
||||||
|
const item = this.findItem(uploadId)
|
||||||
|
if (!item) return
|
||||||
|
|
||||||
|
item.status = 3 // 设置为上传失败状态
|
||||||
|
|
||||||
|
// 从DialogueStore中移除上传任务
|
||||||
|
const dialogueStore = useDialogueStore()
|
||||||
|
dialogueStore.removeUploadTask(clientUploadId)
|
||||||
|
|
||||||
|
if (item.onProgress) {
|
||||||
|
item.onProgress(-1) // 通知上传失败
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -18,7 +18,7 @@ export function isLoggedIn() {
|
|||||||
*/
|
*/
|
||||||
export function getAccessToken() {
|
export function getAccessToken() {
|
||||||
// return storage.get(AccessToken) || ''
|
// return storage.get(AccessToken) || ''
|
||||||
return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22c9c2f9b60a57573e8b08cdf47105e1ba85550c21fa55526e8a00bf316c623eb67abf749622c48beab908d61d3db7b22ed3eb6aa8a08c77680ad4d8a3458c1e72f97ba2b8480674df77f0501a34e82b58'
|
return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b897a4f2416a772eacd03215226020e2e551cdac98368e42541ee3082dc07317d4ecc6a5dfbbe2a28f8c48ccfae7bc6046c3b9b79c0eb3a1ec4c25f5d766a2f8f01f64da8f70f7dbf63e124ffcf72398d86'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,6 +68,11 @@ export function clipboard(text, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function clipboardImage(src, callback) {
|
export async function clipboardImage(src, callback) {
|
||||||
|
// 在wujie环境下使用主应用的clipboard
|
||||||
|
const clipboardObj = window.__POWERED_BY_WUJIE__
|
||||||
|
? window.parent.navigator.clipboard
|
||||||
|
: navigator.clipboard
|
||||||
|
|
||||||
const { state } = await navigator.permissions.query({
|
const { state } = await navigator.permissions.query({
|
||||||
name: 'clipboard-write'
|
name: 'clipboard-write'
|
||||||
})
|
})
|
||||||
@ -80,7 +85,7 @@ export async function clipboardImage(src, callback) {
|
|||||||
|
|
||||||
// navigator.clipboard.write 仅支持 png 图片
|
// navigator.clipboard.write 仅支持 png 图片
|
||||||
if (blob.type == 'image/png') {
|
if (blob.type == 'image/png') {
|
||||||
await navigator.clipboard.write([
|
await clipboardObj.write([
|
||||||
new ClipboardItem({
|
new ClipboardItem({
|
||||||
[blob.type]: blob
|
[blob.type]: blob
|
||||||
})
|
})
|
||||||
@ -99,13 +104,13 @@ export async function clipboardImage(src, callback) {
|
|||||||
|
|
||||||
canvas.width = img.width
|
canvas.width = img.width
|
||||||
canvas.height = img.height
|
canvas.height = img.height
|
||||||
ctx.drawImage(img, 0, 0)
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
canvas.toBlob(
|
canvas.toBlob(
|
||||||
(blob) => {
|
(blob) => {
|
||||||
const data = [new ClipboardItem({ [blob.type]: blob })]
|
const data = [new ClipboardItem({ [blob.type]: blob })]
|
||||||
|
|
||||||
navigator.clipboard
|
clipboardObj
|
||||||
.write(data)
|
.write(data)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
callback()
|
callback()
|
||||||
|
381
src/utils/db.js
Normal file
381
src/utils/db.js
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
|
||||||
|
import Dexie from 'dexie';
|
||||||
|
|
||||||
|
export const db = new Dexie('chatHistory');
|
||||||
|
|
||||||
|
// 定义数据库表结构和索引
|
||||||
|
// 版本3:优化了索引,提高了查询和排序性能
|
||||||
|
db.version(4).stores({
|
||||||
|
/**
|
||||||
|
* 聊天记录表
|
||||||
|
* - msg_id: 消息唯一ID (主键)
|
||||||
|
* - sequence: 消息序列号,用于排序
|
||||||
|
* - [talk_type+receiver_id]: 复合索引,用于快速查询会话消息
|
||||||
|
* - created_at: 消息创建时间,用于排序
|
||||||
|
* - [talk_type+receiver_id+sequence]: 复合索引,用于高效分页查询
|
||||||
|
*/
|
||||||
|
messages: 'msg_id, sequence, [talk_type+receiver_id], created_at, [talk_type+receiver_id+sequence]',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话表
|
||||||
|
* - ++id: 自增主键
|
||||||
|
* - &index_name: 唯一索引 (talk_type + '_' + receiver_id)
|
||||||
|
* - updated_at: 索引,用于排序
|
||||||
|
* - is_top: 索引,用于置顶排序
|
||||||
|
*/
|
||||||
|
conversations: 'id, &index_name, talk_type, receiver_id, updated_at, unread_num, is_top',
|
||||||
|
});
|
||||||
|
|
||||||
|
db.on('ready', () => {
|
||||||
|
console.log(`数据库已就绪,版本: ${db.verno}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 消息类型常量 */
|
||||||
|
export const MessageType = {
|
||||||
|
TEXT: 1, // 文本消息
|
||||||
|
IMAGE: 2, // 图片消息
|
||||||
|
FILE: 3, // 文件消息
|
||||||
|
AUDIO: 4, // 语音消息
|
||||||
|
VIDEO: 5, // 视频消息
|
||||||
|
LOCATION: 6, // 位置消息
|
||||||
|
CARD: 7, // 名片消息
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 会话类型常量 */
|
||||||
|
export const TalkType = {
|
||||||
|
PRIVATE: 1, // 私聊
|
||||||
|
GROUP: 2, // 群聊
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成一个简单的UUID
|
||||||
|
* @returns {string} UUID
|
||||||
|
*/
|
||||||
|
function generateUUID() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||||
|
const r = (Math.random() * 16) | 0;
|
||||||
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region 消息操作
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加或更新一条聊天记录
|
||||||
|
* @param {object} message - 消息对象
|
||||||
|
* @returns {Promise<string>} 消息ID
|
||||||
|
*/
|
||||||
|
export async function addMessage(message) {
|
||||||
|
try {
|
||||||
|
if (!message.msg_id) {
|
||||||
|
message.msg_id = generateUUID();
|
||||||
|
}
|
||||||
|
if (!message.created_at) {
|
||||||
|
message.created_at = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 put 方法,如果主键已存在则更新,否则添加
|
||||||
|
await db.messages.put(message);
|
||||||
|
return message.msg_id;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加或更新消息失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加或更新聊天记录
|
||||||
|
* @param {Array<object>} messages - 消息对象数组
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function batchAddOrUpdateMessages(messages) {
|
||||||
|
try {
|
||||||
|
if (!Array.isArray(messages) || messages.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messagesToStore = messages.map(message => {
|
||||||
|
if (!message.msg_id) {
|
||||||
|
message.msg_id = generateUUID();
|
||||||
|
}
|
||||||
|
if (!message.created_at) {
|
||||||
|
message.created_at = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.messages.bulkPut(messagesToStore);
|
||||||
|
|
||||||
|
// 更新最后一条消息到会话
|
||||||
|
const latestMessage = messagesToStore[messagesToStore.length - 1];
|
||||||
|
if (latestMessage) {
|
||||||
|
await updateConversationLastMessage(latestMessage);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量添加或更新消息失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定会话的聊天记录
|
||||||
|
* @param {number} talkType - 会话类型 (1:私聊, 2:群聊)
|
||||||
|
* @param {number} userId - 当前用户ID
|
||||||
|
* @param {number} receiverId - 接收者ID (私聊为对方用户ID,群聊为群ID)
|
||||||
|
* @param {number} [limit=30] - 限制返回的记录数量
|
||||||
|
* @param {number|null} [maxSequence=null] - 最大sequence值,用于分页加载更早的消息
|
||||||
|
* @returns {Promise<Array<object>>} 消息列表 (按sequence升序排列)
|
||||||
|
*/
|
||||||
|
export async function getMessages(talkType, userId, receiverId, limit = 30, maxSequence = null) {
|
||||||
|
try {
|
||||||
|
let collection;
|
||||||
|
|
||||||
|
if (maxSequence !== null) {
|
||||||
|
// 加载更多:查询 sequence 小于 maxSequence 的消息
|
||||||
|
collection = db.messages
|
||||||
|
.where('[talk_type+receiver_id+sequence]')
|
||||||
|
.between([talkType, receiverId, 0], [talkType, receiverId, maxSequence], true, false);
|
||||||
|
} else {
|
||||||
|
// 首次加载:查询指定会话的所有消息
|
||||||
|
collection = db.messages.where({ '[talk_type+receiver_id]': [talkType, receiverId] });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. reverse() - 利用索引倒序排列,获取最新的消息
|
||||||
|
// 2. limit() - 限制数量,实现分页
|
||||||
|
// 3. toArray() - 执行查询
|
||||||
|
const messages = await collection.reverse().limit(limit).toArray();
|
||||||
|
|
||||||
|
// 再次 reverse() - 将获取到的分页消息按时间正序排列,以便于在界面上显示
|
||||||
|
return messages.reverse();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取消息失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记指定会话的所有消息为已读
|
||||||
|
* @param {number} talkType - 会话类型
|
||||||
|
* @param {number} userId - 当前用户ID
|
||||||
|
* @param {number} receiverId - 接收者ID
|
||||||
|
* @returns {Promise<number>} 更新的消息数量
|
||||||
|
*/
|
||||||
|
export async function markMessagesAsRead(talkType, userId, receiverId) {
|
||||||
|
try {
|
||||||
|
let query;
|
||||||
|
if (talkType === TalkType.PRIVATE) {
|
||||||
|
// 私聊:只标记对方发给我的未读消息
|
||||||
|
query = db.messages
|
||||||
|
.where('[talk_type+receiver_id]')
|
||||||
|
.equals([talkType, userId])
|
||||||
|
.and(item => item.user_id === receiverId && item.is_read === 0);
|
||||||
|
} else {
|
||||||
|
// 群聊:标记群里所有非自己的未读消息
|
||||||
|
query = db.messages
|
||||||
|
.where('[talk_type+receiver_id]')
|
||||||
|
.equals([talkType, receiverId])
|
||||||
|
.and(item => item.user_id !== userId && item.is_read === 0);
|
||||||
|
}
|
||||||
|
return await query.modify({ is_read: 1 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量标记消息已读失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回消息
|
||||||
|
* @param {string} msgId - 消息ID
|
||||||
|
* @returns {Promise<number>} 更新记录数 (1或0)
|
||||||
|
*/
|
||||||
|
export async function revokeMessage(msgId) {
|
||||||
|
try {
|
||||||
|
return await db.messages.update(msgId, { is_revoke: 1 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('撤回消息失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除消息
|
||||||
|
* @param {string} msgId - 消息ID
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function deleteMessage(msgId) {
|
||||||
|
try {
|
||||||
|
await db.messages.delete(msgId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除消息失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion 消息操作
|
||||||
|
|
||||||
|
// #region 会话操作
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加或更新会话
|
||||||
|
* @param {object} conversation - 会话对象
|
||||||
|
* @returns {Promise<number>} 会话ID
|
||||||
|
*/
|
||||||
|
export async function addOrUpdateConversation(conversation) {
|
||||||
|
try {
|
||||||
|
// put 方法会根据唯一索引 index_name 自动判断是添加还是更新
|
||||||
|
return await db.conversations.put(conversation);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加或更新会话失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有会话列表
|
||||||
|
* @param {boolean} [includeEmpty=false] - 是否包含没有最后一条消息的会话
|
||||||
|
* @returns {Promise<Array<object>>} 会话列表 (按置顶和更新时间排序)
|
||||||
|
*/
|
||||||
|
export async function getConversations(includeEmpty = false) {
|
||||||
|
try {
|
||||||
|
const filterFn = item => !includeEmpty ? (item.msg_text && item.msg_text.length > 0) : true;
|
||||||
|
|
||||||
|
// 分别查询置顶和非置顶会话,以利用索引并优化性能
|
||||||
|
const topConversationsPromise = db.conversations
|
||||||
|
.where('is_top')
|
||||||
|
.equals(1)
|
||||||
|
.sortBy('updated_at')
|
||||||
|
.then(arr => arr.reverse().filter(filterFn));
|
||||||
|
|
||||||
|
const otherConversationsPromise = db.conversations
|
||||||
|
.where('is_top')
|
||||||
|
.notEqual(1)
|
||||||
|
.sortBy('updated_at')
|
||||||
|
.then(arr => arr.reverse().filter(filterFn));
|
||||||
|
|
||||||
|
const [topConversations, otherConversations] = await Promise.all([
|
||||||
|
topConversationsPromise,
|
||||||
|
otherConversationsPromise,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [...topConversations, ...otherConversations];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取会话列表失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定会话
|
||||||
|
* @param {number} talkType - 会话类型
|
||||||
|
* @param {number} receiverId - 接收者ID
|
||||||
|
* @returns {Promise<object|undefined>} 会话对象
|
||||||
|
*/
|
||||||
|
export async function getConversation(talkType, receiverId) {
|
||||||
|
try {
|
||||||
|
const indexName = `${talkType}_${receiverId}`;
|
||||||
|
return await db.conversations.get({ index_name: indexName });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取会话失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新会话的未读消息数
|
||||||
|
* @param {number} talkType - 会话类型
|
||||||
|
* @param {number} receiverId - 接收者ID
|
||||||
|
* @param {number|null} unreadNum - 未读消息数。如果为null,则自增1
|
||||||
|
* @returns {Promise<number>} 更新的记录数
|
||||||
|
*/
|
||||||
|
export async function updateConversationUnreadNum(talkType, receiverId, unreadNum = null) {
|
||||||
|
try {
|
||||||
|
const indexName = `${talkType}_${receiverId}`;
|
||||||
|
const conversation = await db.conversations.get({ index_name: indexName });
|
||||||
|
|
||||||
|
if (conversation) {
|
||||||
|
const newUnreadNum = unreadNum === null ? (conversation.unread_num || 0) + 1 : unreadNum;
|
||||||
|
return await db.conversations.update(conversation.id, { unread_num: newUnreadNum });
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新会话未读数失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空会话的未读消息数
|
||||||
|
* @param {number} talkType - 会话类型
|
||||||
|
* @param {number} receiverId - 接收者ID
|
||||||
|
* @returns {Promise<number>} 更新的记录数
|
||||||
|
*/
|
||||||
|
export function clearConversationUnreadNum(talkType, receiverId) {
|
||||||
|
return updateConversationUnreadNum(talkType, receiverId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除会话及其相关的消息
|
||||||
|
* @param {number} conversationId - 会话ID
|
||||||
|
* @param {boolean} [deleteMessages=false] - 是否同时删除相关的消息记录
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function deleteConversation(conversationId, deleteMessages = false) {
|
||||||
|
try {
|
||||||
|
await db.transaction('rw', db.conversations, db.messages, async () => {
|
||||||
|
const conversation = await db.conversations.get(conversationId);
|
||||||
|
if (!conversation) return;
|
||||||
|
|
||||||
|
// 删除会话
|
||||||
|
await db.conversations.delete(conversationId);
|
||||||
|
|
||||||
|
// 如果需要,删除关联的消息
|
||||||
|
if (deleteMessages) {
|
||||||
|
const { talk_type, receiver_id } = conversation;
|
||||||
|
await db.messages.where({ '[talk_type+receiver_id]': [talk_type, receiver_id] }).delete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除会话失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新会话的最后一条消息摘要
|
||||||
|
* @param {object} message - 消息对象
|
||||||
|
* @returns {Promise<number>} 更新的记录数
|
||||||
|
*/
|
||||||
|
export async function updateConversationLastMessage(message) {
|
||||||
|
try {
|
||||||
|
const { talk_type, user_id, receiver_id, msg_type } = message;
|
||||||
|
const targetReceiverId = talk_type === TalkType.PRIVATE ? (user_id === receiver_id ? user_id : receiver_id) : receiver_id;
|
||||||
|
const indexName = `${talk_type}_${targetReceiverId}`;
|
||||||
|
|
||||||
|
const conversation = await db.conversations.get({ index_name: indexName });
|
||||||
|
if (!conversation) return 0;
|
||||||
|
|
||||||
|
let msgText = '';
|
||||||
|
switch (msg_type) {
|
||||||
|
case MessageType.TEXT: msgText = message.content || ''; break;
|
||||||
|
case MessageType.IMAGE: msgText = '[图片]'; break;
|
||||||
|
case MessageType.FILE: msgText = '[文件]'; break;
|
||||||
|
case MessageType.AUDIO: msgText = '[语音]'; break;
|
||||||
|
case MessageType.VIDEO: msgText = '[视频]'; break;
|
||||||
|
case MessageType.LOCATION: msgText = '[位置]'; break;
|
||||||
|
case MessageType.CARD: msgText = '[名片]'; break;
|
||||||
|
default: msgText = '[未知消息]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return await db.conversations.update(conversation.id, {
|
||||||
|
msg_text: msgText,
|
||||||
|
content: message.content || '',
|
||||||
|
updated_at: message.created_at,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新会话最后消息失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion 会话操作
|
@ -25,15 +25,15 @@ const errorHandler = (error) => {
|
|||||||
|
|
||||||
if (!once) {
|
if (!once) {
|
||||||
once = true
|
once = true
|
||||||
window['$dialog'].info({
|
// window['$dialog'].info({
|
||||||
title: '友情提示',
|
// title: '友情提示',
|
||||||
content: '当前登录已失效,请重新登录?',
|
// content: '当前登录已失效,请重新登录?',
|
||||||
positiveText: '立即登录?',
|
// positiveText: '立即登录?',
|
||||||
maskClosable: false,
|
// maskClosable: false,
|
||||||
onPositiveClick: () => {
|
// onPositiveClick: () => {
|
||||||
location.reload()
|
// location.reload()
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +53,12 @@ request.interceptors.request.use((config) => {
|
|||||||
}, errorHandler)
|
}, errorHandler)
|
||||||
|
|
||||||
// 响应拦截器
|
// 响应拦截器
|
||||||
request.interceptors.response.use((response) => response.data, errorHandler)
|
request.interceptors.response.use((response) => {
|
||||||
|
if(response.data.code !==200&&response.data.status!==0){
|
||||||
|
window['$message'].warning(response.data.msg)
|
||||||
|
}
|
||||||
|
return response.data
|
||||||
|
}, errorHandler)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET 请求
|
* GET 请求
|
||||||
|
@ -12,12 +12,14 @@ import customModal from '@/components/common/customModal.vue'
|
|||||||
import historyRecord from '@/components/search/searchByCondition.vue'
|
import historyRecord from '@/components/search/searchByCondition.vue'
|
||||||
import { ServeEditGroupNotice, ServeGetGroupNotices, ServeDeleteGroupNotice } from '@/api/group'
|
import { ServeEditGroupNotice, ServeGetGroupNotices, ServeDeleteGroupNotice } from '@/api/group'
|
||||||
import avatarModule from '@/components/avatar-module/index.vue'
|
import avatarModule from '@/components/avatar-module/index.vue'
|
||||||
|
import { ServeCheckFriend, ServeAddFriend } from '@/api/chat'
|
||||||
|
import { useUtil } from '@/hooks/useUtil'
|
||||||
|
|
||||||
|
const { useMessage } = useUtil()
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const dialogueStore = useDialogueStore()
|
const dialogueStore = useDialogueStore()
|
||||||
const uploadsStore = useUploadsStore()
|
const uploadsStore = useUploadsStore()
|
||||||
console.log('dialogueStore', dialogueStore)
|
|
||||||
|
|
||||||
const members = computed(() => dialogueStore.members)
|
const members = computed(() => dialogueStore.members)
|
||||||
const membersByAlphabet = computed(() => {
|
const membersByAlphabet = computed(() => {
|
||||||
if (state.searchMemberByAlphabet) {
|
if (state.searchMemberByAlphabet) {
|
||||||
@ -122,9 +124,31 @@ const events = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const isFriend = ref(true) // 是否为好友
|
||||||
|
// // 添加好友
|
||||||
|
// const AddFriends = () => {
|
||||||
|
// let params = {
|
||||||
|
// receiver_id: talkParams.receiver_id, //聊天的用户id
|
||||||
|
// talk_type: 1
|
||||||
|
// }
|
||||||
|
// ServeAddFriend(params).then((res) => {
|
||||||
|
// if (res?.code === 200) {
|
||||||
|
// isFriend.value = !isFriend.value
|
||||||
|
// useMessage.success('添加成功')
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
watch(
|
watch(
|
||||||
() => talkParams,
|
() => talkParams,
|
||||||
(newValue, oldValue) => {
|
(newValue, oldValue) => {
|
||||||
|
// 判断是否为好友
|
||||||
|
// if (talkParams.type !== 2) {
|
||||||
|
// ServeCheckFriend({ receiver_id: newValue.receiver_id, talk_type: 1 }).then((res) => {
|
||||||
|
// if (res?.code === 200) {
|
||||||
|
// isFriend.value = res.data.is_friend
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
console.log(newValue)
|
console.log(newValue)
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
@ -510,7 +534,27 @@ const clearSelectedDateTime = () => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- 聊天区域 -->
|
<!-- 聊天区域 -->
|
||||||
<main class="el-main">
|
<main class="el-main relative">
|
||||||
|
<!-- <div
|
||||||
|
class="p-[15px] pt-[10px] w-[100%] z-99 absolute"
|
||||||
|
v-if="!isFriend && talkParams.type !== 2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-[#FFFFFF] w-[100%] p-[10px] text-[14px] flex justify-between"
|
||||||
|
style="box-shadow: 0 2px 6px 1px rgba(0, 0, 0, 0.2) !important; border-radius: 5px"
|
||||||
|
>
|
||||||
|
对方还不是您的好友,请添加到通讯录中吧!
|
||||||
|
<n-button
|
||||||
|
@click="AddFriends"
|
||||||
|
size="tiny"
|
||||||
|
type="success"
|
||||||
|
color="#46299D"
|
||||||
|
text-color="#ffffff"
|
||||||
|
>
|
||||||
|
<span>添加好友</span>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
<PanelContent
|
<PanelContent
|
||||||
:uid="talkParams.uid"
|
:uid="talkParams.uid"
|
||||||
:talk_type="talkParams.type"
|
:talk_type="talkParams.type"
|
||||||
@ -595,7 +639,7 @@ const clearSelectedDateTime = () => {
|
|||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="search-record-modal-searchArea">
|
<div class="search-record-modal-searchArea">
|
||||||
<n-card style="padding: 0 12px;">
|
<n-card style="padding: 0 12px">
|
||||||
<div class="search-record-input">
|
<div class="search-record-input">
|
||||||
<span class="search-record-input-title">搜索</span>
|
<span class="search-record-input-title">搜索</span>
|
||||||
<n-input
|
<n-input
|
||||||
@ -625,7 +669,7 @@ const clearSelectedDateTime = () => {
|
|||||||
v-model:show="state.showDateConditionPopover"
|
v-model:show="state.showDateConditionPopover"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
style="height: 312px; padding: 0;"
|
style="height: 312px; padding: 0"
|
||||||
@update:show="onDatePickShow"
|
@update:show="onDatePickShow"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@ -653,7 +697,7 @@ const clearSelectedDateTime = () => {
|
|||||||
v-model:show="state.showMemberListByAlphabetPopover"
|
v-model:show="state.showMemberListByAlphabetPopover"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
style="width: 290px; height: 505px; padding: 0;"
|
style="width: 290px; height: 505px; padding: 0"
|
||||||
v-if="talkParams.type === 2"
|
v-if="talkParams.type === 2"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@ -662,10 +706,10 @@ const clearSelectedDateTime = () => {
|
|||||||
<div class="member-list-by-alphabet-container">
|
<div class="member-list-by-alphabet-container">
|
||||||
<n-input
|
<n-input
|
||||||
placeholder="请输入群成员"
|
placeholder="请输入群成员"
|
||||||
style="margin: 0 0 17px;"
|
style="margin: 0 0 17px"
|
||||||
v-model:value="state.searchMemberByAlphabet"
|
v-model:value="state.searchMemberByAlphabet"
|
||||||
/>
|
/>
|
||||||
<n-scrollbar style="height: 430px;">
|
<n-scrollbar style="height: 430px">
|
||||||
<div
|
<div
|
||||||
class="member-list-by-alphabet"
|
class="member-list-by-alphabet"
|
||||||
v-for="(alphabetMembersItem, alphabetMembersIndex) in membersByAlphabet"
|
v-for="(alphabetMembersItem, alphabetMembersIndex) in membersByAlphabet"
|
||||||
@ -677,7 +721,8 @@ const clearSelectedDateTime = () => {
|
|||||||
<div class="member-list-each-alphabet">
|
<div class="member-list-each-alphabet">
|
||||||
<div
|
<div
|
||||||
class="member-item-each-alphabet"
|
class="member-item-each-alphabet"
|
||||||
v-for="(memberItem, memberItemIndex) in (alphabetMembersItem as any).members"
|
v-for="(memberItem, memberItemIndex) in (alphabetMembersItem as any)
|
||||||
|
.members"
|
||||||
:key="memberItemIndex"
|
:key="memberItemIndex"
|
||||||
@click="handleMemberItemClick(memberItem)"
|
@click="handleMemberItemClick(memberItem)"
|
||||||
>
|
>
|
||||||
@ -778,7 +823,7 @@ const clearSelectedDateTime = () => {
|
|||||||
}"
|
}"
|
||||||
></avatarModule>
|
></avatarModule>
|
||||||
<div class="group-notice-header-userInfo">
|
<div class="group-notice-header-userInfo">
|
||||||
<span style="color: #1b1b1b; font-weight: 600; line-height: 20px;">{{
|
<span style="color: #1b1b1b; font-weight: 600; line-height: 20px">{{
|
||||||
state.groupNoticeInfo.updater_name
|
state.groupNoticeInfo.updater_name
|
||||||
}}</span>
|
}}</span>
|
||||||
<span>{{ state.groupNoticeInfo.updated_at }}</span>
|
<span>{{ state.groupNoticeInfo.updated_at }}</span>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="tsx" setup>
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
ref,
|
ref,
|
||||||
@ -23,10 +23,10 @@ import {
|
|||||||
NButton,
|
NButton,
|
||||||
NPagination
|
NPagination
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { Search, Plus, Right } from '@icon-park/vue-next'
|
import { Search, Plus, Right, AddOne, PeoplePlusOne } from '@icon-park/vue-next'
|
||||||
import TalkItem from './TalkItem.vue'
|
import TalkItem from './TalkItem.vue'
|
||||||
import Skeleton from './Skeleton.vue'
|
import Skeleton from './Skeleton.vue'
|
||||||
import { ServeClearTalkUnreadNum } from '@/api/chat'
|
import { ServeClearTalkUnreadNum, ServeAddFriend, GetFriendList } from '@/api/chat'
|
||||||
import GroupLaunch from '@/components/group/GroupLaunch.vue'
|
import GroupLaunch from '@/components/group/GroupLaunch.vue'
|
||||||
import { getCacheIndexName } from '@/utils/talk'
|
import { getCacheIndexName } from '@/utils/talk'
|
||||||
import { ISession } from '@/types/chat'
|
import { ISession } from '@/types/chat'
|
||||||
@ -38,9 +38,15 @@ import flTree from '@/components/flnlayout/tree/flnindex.vue'
|
|||||||
import { processError, processSuccess } from '@/utils/helper/message.js'
|
import { processError, processSuccess } from '@/utils/helper/message.js'
|
||||||
import chatAppSearchList from '@/components/search/searchList.vue'
|
import chatAppSearchList from '@/components/search/searchList.vue'
|
||||||
import { ServeSeachQueryAll, ServeQueryTalkRecord, ServeUserGroupChatList } from '@/api/search'
|
import { ServeSeachQueryAll, ServeQueryTalkRecord, ServeUserGroupChatList } from '@/api/search'
|
||||||
|
import { GetContactFriendList } from '@/api/chat'
|
||||||
import { getUserInfoByERPUserId } from '@/api/user'
|
import { getUserInfoByERPUserId } from '@/api/user'
|
||||||
import HighlightText from '@/components/search/highLightText.vue'
|
import HighlightText from '@/components/search/highLightText.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import icon from '@/assets/image/chatList/addressBook.png'
|
||||||
|
import { useUtil } from '@/hooks/useUtil'
|
||||||
|
import UserCardModal from '@/components/user/UserCardModal.vue'
|
||||||
|
|
||||||
|
const { useMessage } = useUtil()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const currentInstance = getCurrentInstance()
|
const currentInstance = getCurrentInstance()
|
||||||
@ -60,7 +66,43 @@ const isShowGroup = ref(false)
|
|||||||
const searchKeyword = ref('')
|
const searchKeyword = ref('')
|
||||||
const topItems = computed((): ISession[] => talkStore.topItems)
|
const topItems = computed((): ISession[] => talkStore.topItems)
|
||||||
const unreadNum = computed(() => talkStore.talkUnreadNum)
|
const unreadNum = computed(() => talkStore.talkUnreadNum)
|
||||||
|
// 是否删除好友弹框
|
||||||
|
const handleConfirmDel = (row) => {
|
||||||
|
window['$dialog'].create({
|
||||||
|
title: '温馨提示',
|
||||||
|
content: '是否删除该好友?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: () => {
|
||||||
|
console.log('确定')
|
||||||
|
let params = {
|
||||||
|
receiver_id: row.id, //聊天的用户id
|
||||||
|
talk_type: 1
|
||||||
|
}
|
||||||
|
let url = '/api/v1/contact/friend/delete'
|
||||||
|
$request.HTTP.components.postDataByParams(url, params).then((res) => {
|
||||||
|
// console.log(res)
|
||||||
|
if (res?.code === 200) {
|
||||||
|
useMessage.success('删除成功')
|
||||||
|
getMyFriends()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = ref([
|
||||||
|
{
|
||||||
|
label: '添加好友',
|
||||||
|
key: 'addFriend',
|
||||||
|
icon: () => <n-icon size="20" component={PeoplePlusOne} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '通讯录',
|
||||||
|
key: 'addressBook',
|
||||||
|
icon: () => <img style="width: 19px; height: 20px; cursor: pointer" src={icon} />
|
||||||
|
}
|
||||||
|
])
|
||||||
//自定义搜索
|
//自定义搜索
|
||||||
const renderChatAppSearch = () => {
|
const renderChatAppSearch = () => {
|
||||||
return h(
|
return h(
|
||||||
@ -145,7 +187,13 @@ const renderChatAppSearch = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
userInfo: {
|
||||||
|
isShowUserCardModal: false,
|
||||||
|
user_id: NaN,
|
||||||
|
erp_user_id: NaN
|
||||||
|
},
|
||||||
isShowAddressBookModal: false, // 是否显示通讯录模态框
|
isShowAddressBookModal: false, // 是否显示通讯录模态框
|
||||||
|
isShowAddFriendModal: false, // 是否显示添加好友模态框
|
||||||
customModalStyle: {
|
customModalStyle: {
|
||||||
width: '1288px',
|
width: '1288px',
|
||||||
height: '846px',
|
height: '846px',
|
||||||
@ -166,7 +214,25 @@ const state = reactive({
|
|||||||
type: 'input',
|
type: 'input',
|
||||||
valueType: 'string'
|
valueType: 'string'
|
||||||
}
|
}
|
||||||
], // 群聊列表搜索配置
|
],
|
||||||
|
// 我的好友搜索配置
|
||||||
|
myFriendSearchConfig: [
|
||||||
|
{
|
||||||
|
label: '好友名称',
|
||||||
|
key: 'myFriendname',
|
||||||
|
type: 'input',
|
||||||
|
valueType: 'string'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
addFriendSearchConfig: [
|
||||||
|
{
|
||||||
|
label: '姓名',
|
||||||
|
key: 'friendName',
|
||||||
|
type: 'input',
|
||||||
|
valueType: 'string'
|
||||||
|
}
|
||||||
|
], // 添加好友搜索配置
|
||||||
|
// 群聊列表搜索配置
|
||||||
treeData: [],
|
treeData: [],
|
||||||
expandedKeys: [],
|
expandedKeys: [],
|
||||||
clickKey: 3,
|
clickKey: 3,
|
||||||
@ -175,18 +241,19 @@ const state = reactive({
|
|||||||
addressBookColumns: [
|
addressBookColumns: [
|
||||||
{
|
{
|
||||||
title: '姓名 【工号】',
|
title: '姓名 【工号】',
|
||||||
field: 'nickName',
|
field: 'nickname',
|
||||||
width: 200,
|
width: 200,
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
tooltip: true
|
tooltip: true
|
||||||
},
|
},
|
||||||
render(row, index) {
|
render(row, index) {
|
||||||
|
// return row.nickname + '【' + row.job_num + '】'
|
||||||
return row.nickName + '【' + row.jobNum + '】'
|
return row.nickName + '【' + row.jobNum + '】'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '岗位名称',
|
title: '岗位名称',
|
||||||
field: 'positionName',
|
field: 'user_position',
|
||||||
width: 400,
|
width: 400,
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
tooltip: true
|
tooltip: true
|
||||||
@ -198,6 +265,7 @@ const state = reactive({
|
|||||||
)
|
)
|
||||||
: []
|
: []
|
||||||
return positionNames.join(' , ')
|
return positionNames.join(' , ')
|
||||||
|
// return row.user_position.map((item) => item.position_name).join(' , ')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -270,8 +338,124 @@ const state = reactive({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
], // 群聊列表表格列
|
], // 群聊列表表格列
|
||||||
|
myFriendListColumns: [
|
||||||
|
{
|
||||||
|
title: '姓名 【工号】',
|
||||||
|
field: 'nickname',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
|
render(row, index) {
|
||||||
|
return row.nickname + '【' + row.job_num + '】'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '岗位名称',
|
||||||
|
field: 'user_position',
|
||||||
|
width: 400,
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
|
render(row, index) {
|
||||||
|
// let positionNames = Array.isArray(row.user_position)
|
||||||
|
// ? row.depPositions.flatMap((dep) =>
|
||||||
|
// Array.isArray(dep.positions) ? dep.positions.map((pos) => pos.name) : []
|
||||||
|
// )
|
||||||
|
// : []
|
||||||
|
return row.user_position.map((item) => item.position_name).join(' , ')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
field: 'action',
|
||||||
|
width: 180,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row, index) {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
text: true,
|
||||||
|
color: '#46299d',
|
||||||
|
onClick: () => handleEnterChat(row)
|
||||||
|
},
|
||||||
|
{ default: () => '进入聊天' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
text: true,
|
||||||
|
color: '#46299d',
|
||||||
|
class: 'pl-[10px]',
|
||||||
|
onClick: () => handleConfirmDel(row)
|
||||||
|
},
|
||||||
|
{ default: () => '删除好友' }
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
], // 我的好友表格列
|
||||||
|
addFriendListColumns: [
|
||||||
|
{
|
||||||
|
title: '姓名 【工号】',
|
||||||
|
field: 'nickname',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
|
render(row, index) {
|
||||||
|
return row.nickname + '【' + row.job_num + '】'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '岗位名称',
|
||||||
|
field: 'user_position',
|
||||||
|
width: 400,
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
|
render(row, index) {
|
||||||
|
// let positionNames = Array.isArray(row.user_position)
|
||||||
|
// ? row.depPositions.flatMap((dep) =>
|
||||||
|
// Array.isArray(dep.positions) ? dep.positions.map((pos) => pos.name) : []
|
||||||
|
// )
|
||||||
|
// : []
|
||||||
|
return row.user_position.map((item) => item.position_name).join(' , ')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
field: 'action',
|
||||||
|
width: 180,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row, index) {
|
||||||
|
return h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
text: true,
|
||||||
|
color: '#46299d',
|
||||||
|
onClick: () => {
|
||||||
|
state.userInfo.user_id = row.id
|
||||||
|
state.userInfo.erp_user_id = row.erp_user_id
|
||||||
|
state.userInfo.isShowUserCardModal = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ default: () => '查看' }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
], // 添加表格列
|
||||||
addressBookData: [], // 通讯录表格数据
|
addressBookData: [], // 通讯录表格数据
|
||||||
|
company_name: '', // 当前公司别
|
||||||
groupChatListData: [], // 群聊列表表格数据
|
groupChatListData: [], // 群聊列表表格数据
|
||||||
|
myFriendListData: [], // 我的好友表格数据
|
||||||
|
addFriendList: [], // 搜索出来的可添加好友
|
||||||
addressBookTableHeight: 524, // 通讯录表格高度
|
addressBookTableHeight: 524, // 通讯录表格高度
|
||||||
addressBookTableWidth: 800, // 通讯录表格宽度
|
addressBookTableWidth: 800, // 通讯录表格宽度
|
||||||
addressBookPage: 1, // 通讯录表格页码
|
addressBookPage: 1, // 通讯录表格页码
|
||||||
@ -283,6 +467,10 @@ const state = reactive({
|
|||||||
groupChatListPageSize: 10, // 群聊列表表格每页条数
|
groupChatListPageSize: 10, // 群聊列表表格每页条数
|
||||||
groupChatListTotal: 0, // 群聊列表表格总条数
|
groupChatListTotal: 0, // 群聊列表表格总条数
|
||||||
groupChatListSearchGroupName: '', // 群聊列表搜索条件-群聊名称
|
groupChatListSearchGroupName: '', // 群聊列表搜索条件-群聊名称
|
||||||
|
myFriendListPage: 1, // 我的好友表格页码
|
||||||
|
myFriendListPageSize: 10, // 我的好友表格每页条数
|
||||||
|
myFriendListTotal: 0, // 我的好友表格总条数
|
||||||
|
myFriendListSearchName: '', // 我的好友搜索条件-好友名称
|
||||||
chatSearchOptions: [
|
chatSearchOptions: [
|
||||||
{
|
{
|
||||||
key: 'chatSearch',
|
key: 'chatSearch',
|
||||||
@ -334,6 +522,9 @@ const items = computed((): ISession[] => {
|
|||||||
|
|
||||||
return [...topItems, ...normalItems]
|
return [...topItems, ...normalItems]
|
||||||
})
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('items', items)
|
||||||
|
}, 2000)
|
||||||
watch(
|
watch(
|
||||||
() => state.addressBookSearchNickName,
|
() => state.addressBookSearchNickName,
|
||||||
(newValue, oldValue) => {
|
(newValue, oldValue) => {
|
||||||
@ -350,6 +541,17 @@ watch(
|
|||||||
getDepPoisUser()
|
getDepPoisUser()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
watch(
|
||||||
|
() => state.myFriendListSearchName,
|
||||||
|
(newValue, oldValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
state.myFriendListPage = 1
|
||||||
|
} else {
|
||||||
|
state.myFriendListPage = 1
|
||||||
|
}
|
||||||
|
getMyFriends()
|
||||||
|
}
|
||||||
|
)
|
||||||
watch(
|
watch(
|
||||||
() => state.groupChatListSearchGroupName,
|
() => state.groupChatListSearchGroupName,
|
||||||
(newValue, oldValue) => {
|
(newValue, oldValue) => {
|
||||||
@ -397,7 +599,7 @@ const indexName = computed(() => dialogueStore.index_name)
|
|||||||
// 切换会话
|
// 切换会话
|
||||||
const onTabTalk = (item: ISession, follow = false) => {
|
const onTabTalk = (item: ISession, follow = false) => {
|
||||||
console.log('onTabTalk')
|
console.log('onTabTalk')
|
||||||
|
console.log('item.index_name === indexName.value', item.index_name === indexName.value)
|
||||||
if (item.index_name === indexName.value) return
|
if (item.index_name === indexName.value) return
|
||||||
|
|
||||||
searchKeyword.value = ''
|
searchKeyword.value = ''
|
||||||
@ -442,7 +644,7 @@ const onReload = () => {
|
|||||||
// 初始化加载
|
// 初始化加载
|
||||||
const onInitialize = () => {
|
const onInitialize = () => {
|
||||||
let index_name = getCacheIndexName()
|
let index_name = getCacheIndexName()
|
||||||
|
console.log('index_name', index_name)
|
||||||
index_name && onTabTalk(talkStore.findItem(index_name), true)
|
index_name && onTabTalk(talkStore.findItem(index_name), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,6 +653,8 @@ onBeforeRouteUpdate(onInitialize)
|
|||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
getTreeData()
|
getTreeData()
|
||||||
|
getDepPoisUser()
|
||||||
|
// getMyFriends()
|
||||||
getUserGroupChatList()
|
getUserGroupChatList()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -462,11 +666,24 @@ onMounted(() => {
|
|||||||
const showAddressBookModal = () => {
|
const showAddressBookModal = () => {
|
||||||
state.isShowAddressBookModal = true
|
state.isShowAddressBookModal = true
|
||||||
}
|
}
|
||||||
|
// 点击显示添加好友模态框
|
||||||
|
const showAddFriendModal = () => {
|
||||||
|
state.isShowAddFriendModal = true
|
||||||
|
}
|
||||||
|
const handleSelect = (key: string | number) => {
|
||||||
|
if (key === 'addressBook') return showAddressBookModal()
|
||||||
|
showAddFriendModal()
|
||||||
|
}
|
||||||
// 点击关闭通讯录模态框
|
// 点击关闭通讯录模态框
|
||||||
const closeAddressBookModal = () => {
|
const closeAddressBookModal = () => {
|
||||||
state.isShowAddressBookModal = false
|
state.isShowAddressBookModal = false
|
||||||
resetAddressBookModal()
|
resetAddressBookModal()
|
||||||
}
|
}
|
||||||
|
// 点击关闭添加好友模态框
|
||||||
|
const closeAddFriendModal = () => {
|
||||||
|
state.isShowAddFriendModal = false
|
||||||
|
resetAddressBookModal()
|
||||||
|
}
|
||||||
const handleTreeClick = ({ selectedKey, tree }) => {
|
const handleTreeClick = ({ selectedKey, tree }) => {
|
||||||
// console.log(tree)
|
// console.log(tree)
|
||||||
state.clickKey = tree.key
|
state.clickKey = tree.key
|
||||||
@ -488,7 +705,7 @@ const calcTreeData = (data) => {
|
|||||||
delete item.sons
|
delete item.sons
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 获取组织树数据
|
// 获取组织树数据-已隐藏
|
||||||
const getTreeData = () => {
|
const getTreeData = () => {
|
||||||
let url = '/department/v2/tree/filter'
|
let url = '/department/v2/tree/filter'
|
||||||
let params = {}
|
let params = {}
|
||||||
@ -512,9 +729,32 @@ const getTreeData = () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// 获取我的好友
|
||||||
|
const getMyFriends = () => {
|
||||||
|
// myFriendListPage: 1, // 我的好友表格页码
|
||||||
|
// myFriendListPageSize: 10, // 我的好友表格每页条数
|
||||||
|
// myFriendListTotal: 0, // 我的好友表格总条数
|
||||||
|
// myFriendListSearchName: '', // 我的好友搜索条件-好友名称
|
||||||
|
let params = {
|
||||||
|
type: 'myFriends', //查我得好友的时候写死myFriends
|
||||||
|
page: state.myFriendListPage,
|
||||||
|
page_size: state.myFriendListPageSize,
|
||||||
|
name: state.myFriendListSearchName
|
||||||
|
}
|
||||||
|
// let url = '/api/v1/contact/friend/list'
|
||||||
|
GetContactFriendList(params).then((res) => {
|
||||||
|
// console.log(res)
|
||||||
|
if (res.code === 200 && Array.isArray(res.data.user_list)) {
|
||||||
|
state.myFriendListData = res.data.user_list || []
|
||||||
|
state.company_name = res.data.company_name || ''
|
||||||
|
state.myFriendListTotal = res.data.count
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
// 获取部门下的人员
|
// 获取部门下的人员
|
||||||
const getDepPoisUser = () => {
|
const getDepPoisUser = () => {
|
||||||
let url = '/user/v2/list'
|
let url = '/user/v2/list'
|
||||||
|
// let url = '/api/v1/contact/friend/list'
|
||||||
let params = {
|
let params = {
|
||||||
departmentId: state.addressBookSearchNickName ? undefined : state.clickKey,
|
departmentId: state.addressBookSearchNickName ? undefined : state.clickKey,
|
||||||
page: state.addressBookPage,
|
page: state.addressBookPage,
|
||||||
@ -529,11 +769,42 @@ const getDepPoisUser = () => {
|
|||||||
state.addressBookTotal = res.data.count
|
state.addressBookTotal = res.data.count
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// let params = {
|
||||||
|
// type: 'addressBook', //查我的通讯录的时候写死addressBook
|
||||||
|
// page: state.addressBookPage,
|
||||||
|
// page_size: state.addressBookPageSize,
|
||||||
|
// name: state.addressBookSearchNickName
|
||||||
|
// }
|
||||||
|
// GetContactFriendList(params).then((res) => {
|
||||||
|
// // console.log(res)
|
||||||
|
// if (res.code === 200 && Array.isArray(res.data.user_list)) {
|
||||||
|
// state.addressBookData = res.data.user_list || []
|
||||||
|
// state.company_name = res.data.company_name || ''
|
||||||
|
// state.addressBookTotal = res.data.count
|
||||||
|
// }
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索可添加好友
|
||||||
|
const AddFriends = (row) => {
|
||||||
|
let params = {
|
||||||
|
receiver_id: row.erp_user_id, //聊天的用户id
|
||||||
|
talk_type: 1
|
||||||
|
}
|
||||||
|
ServeAddFriend(params).then((res) => {
|
||||||
|
if (res?.code === 200) {
|
||||||
|
useMessage.success('添加成功')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//点击进入对应的聊天
|
//点击进入对应的聊天
|
||||||
const handleEnterChat = async (row) => {
|
const handleEnterChat = async (row) => {
|
||||||
console.log(row)
|
console.log(row)
|
||||||
if (state.addressBookCurrentTab === 'employeeAddressBook') {
|
if (
|
||||||
|
state.addressBookCurrentTab === 'employeeAddressBook' ||
|
||||||
|
state.addressBookCurrentTab === 'myFriend'
|
||||||
|
) {
|
||||||
//员工通讯录,聊天类型一定为单聊
|
//员工通讯录,聊天类型一定为单聊
|
||||||
await getUserInfoByERPUserId({ erp_user_id: row.ID }).then((res) => {
|
await getUserInfoByERPUserId({ erp_user_id: row.ID }).then((res) => {
|
||||||
// console.log(res)
|
// console.log(res)
|
||||||
@ -563,7 +834,9 @@ const resetAddressBookModal = () => {
|
|||||||
state.groupChatListPage = 1
|
state.groupChatListPage = 1
|
||||||
state.groupChatListPageSize = 10
|
state.groupChatListPageSize = 10
|
||||||
getDepPoisUser()
|
getDepPoisUser()
|
||||||
|
// getMyFriends()
|
||||||
getUserGroupChatList()
|
getUserGroupChatList()
|
||||||
|
state.addFriendList = []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
//处理页数变化
|
//处理页数变化
|
||||||
@ -599,6 +872,32 @@ const changeGroupChatListSearch = (value) => {
|
|||||||
state.groupChatListSearchGroupName = value.groupName
|
state.groupChatListSearchGroupName = value.groupName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//处理我的好友搜索
|
||||||
|
const changeMyFriendListSearch = (value) => {
|
||||||
|
console.log(value, 'value')
|
||||||
|
if (!value.myFriendname?.trim()) {
|
||||||
|
state.myFriendListSearchName = ''
|
||||||
|
} else {
|
||||||
|
state.myFriendListSearchName = value.myFriendname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const changeAddFriendSearch = (value) => {
|
||||||
|
console.log(11)
|
||||||
|
if (value.friendName?.trim()) {
|
||||||
|
state.myFriendListSearchName = ''
|
||||||
|
// 搜索好友
|
||||||
|
let params = {
|
||||||
|
name: value.friendName
|
||||||
|
}
|
||||||
|
// let url = '/api/v1/contact/friend/search'
|
||||||
|
GetFriendList(params).then((res) => {
|
||||||
|
// console.log(res)
|
||||||
|
if (res.code === 200) {
|
||||||
|
state.addFriendList = res.data?.user_list || []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
//获取用户所在群聊列表
|
//获取用户所在群聊列表
|
||||||
const getUserGroupChatList = () => {
|
const getUserGroupChatList = () => {
|
||||||
let params = {
|
let params = {
|
||||||
@ -628,6 +927,19 @@ const handleGroupChatListPaginationSize = (value) => {
|
|||||||
state.groupChatListPage = 1
|
state.groupChatListPage = 1
|
||||||
getUserGroupChatList()
|
getUserGroupChatList()
|
||||||
}
|
}
|
||||||
|
//处理我的好友页数变化
|
||||||
|
const handleMyFriendListPagination = (value) => {
|
||||||
|
console.log(value, 'value')
|
||||||
|
state.myFriendListPage = value
|
||||||
|
getMyFriends()
|
||||||
|
}
|
||||||
|
//处理我的好友每页条数变化
|
||||||
|
const handleMyFriendListPaginationSize = (value) => {
|
||||||
|
console.log(value, 'value')
|
||||||
|
state.myFriendListPageSize = value
|
||||||
|
state.myFriendListPage = 1
|
||||||
|
getMyFriends()
|
||||||
|
}
|
||||||
//处理搜索聊天记录点击
|
//处理搜索聊天记录点击
|
||||||
const handleClickSearchItem = (searchText, searchResultKey, talk_type, receiver_id, res) => {
|
const handleClickSearchItem = (searchText, searchResultKey, talk_type, receiver_id, res) => {
|
||||||
console.log(searchText, searchResultKey, talk_type, receiver_id)
|
console.log(searchText, searchResultKey, talk_type, receiver_id)
|
||||||
@ -766,7 +1078,7 @@ const handleEnterSearchResultChat = () => {
|
|||||||
<n-dropdown
|
<n-dropdown
|
||||||
trigger="click"
|
trigger="click"
|
||||||
:options="state.chatSearchOptions"
|
:options="state.chatSearchOptions"
|
||||||
style="width: 248px; height: 677px;"
|
style="width: 248px; height: 677px"
|
||||||
:show="state.showSearchDropdown"
|
:show="state.showSearchDropdown"
|
||||||
@clickoutside="state.showSearchDropdown = false"
|
@clickoutside="state.showSearchDropdown = false"
|
||||||
>
|
>
|
||||||
@ -774,7 +1086,7 @@ const handleEnterSearchResultChat = () => {
|
|||||||
placeholder="搜索好友 / 群聊"
|
placeholder="搜索好友 / 群聊"
|
||||||
v-model:value.trim="searchKeyword"
|
v-model:value.trim="searchKeyword"
|
||||||
clearable
|
clearable
|
||||||
style="width: 78%;"
|
style="width: 78%"
|
||||||
@click="state.showSearchDropdown = true"
|
@click="state.showSearchDropdown = true"
|
||||||
>
|
>
|
||||||
<!-- <template #prefix>
|
<!-- <template #prefix>
|
||||||
@ -788,11 +1100,14 @@ const handleEnterSearchResultChat = () => {
|
|||||||
</template>
|
</template>
|
||||||
</n-button> -->
|
</n-button> -->
|
||||||
<img
|
<img
|
||||||
style="width: 19px; height: 20px; cursor: pointer;"
|
style="width: 19px; height: 20px; cursor: pointer"
|
||||||
src="@/assets/image/chatList/addressBook.png"
|
src="@/assets/image/chatList/addressBook.png"
|
||||||
alt=""
|
alt=""
|
||||||
@click="showAddressBookModal"
|
@click="showAddressBookModal"
|
||||||
/>
|
/>
|
||||||
|
<!-- <n-dropdown :options="option" @select="handleSelect">
|
||||||
|
<n-button> <n-icon :component="AddOne" /></n-button>
|
||||||
|
</n-dropdown> -->
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- 置顶栏目 -->
|
<!-- 置顶栏目 -->
|
||||||
@ -836,9 +1151,10 @@ const handleEnterSearchResultChat = () => {
|
|||||||
<main id="talk-session-list" class="el-main me-scrollbar me-scrollbar-thumb">
|
<main id="talk-session-list" class="el-main me-scrollbar me-scrollbar-thumb">
|
||||||
<template v-if="loadStatus == 2"><Skeleton /></template>
|
<template v-if="loadStatus == 2"><Skeleton /></template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<n-virtual-list :item-size="64" :items="items">
|
||||||
|
<template #default="{ item }">
|
||||||
<TalkItem
|
<TalkItem
|
||||||
v-for="item in items"
|
:key="item.index_name + item.unread_num"
|
||||||
:key="item.index_name"
|
|
||||||
:data="item"
|
:data="item"
|
||||||
:avatar="item.avatar"
|
:avatar="item.avatar"
|
||||||
:username="item.remark || item.name"
|
:username="item.remark || item.name"
|
||||||
@ -848,11 +1164,19 @@ const handleEnterSearchResultChat = () => {
|
|||||||
@contextmenu.prevent="onContextMenuTalk($event, item)"
|
@contextmenu.prevent="onContextMenuTalk($event, item)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
</n-virtual-list>
|
||||||
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<GroupLaunch v-if="isShowGroup" @close="isShowGroup = false" @on-submit="onReload" />
|
<GroupLaunch v-if="isShowGroup" @close="isShowGroup = false" @on-submit="onReload" />
|
||||||
|
|
||||||
|
<UserCardModal
|
||||||
|
v-model:show="state.userInfo.isShowUserCardModal"
|
||||||
|
v-model:uid="(state.userInfo as any).user_id"
|
||||||
|
:euid="(state.userInfo as any).erp_user_id"
|
||||||
|
@update:send="closeAddFriendModal"
|
||||||
|
/>
|
||||||
<customModal
|
<customModal
|
||||||
v-model:show="state.isShowAddressBookModal"
|
v-model:show="state.isShowAddressBookModal"
|
||||||
title="通讯录"
|
title="通讯录"
|
||||||
@ -864,13 +1188,17 @@ const handleEnterSearchResultChat = () => {
|
|||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="custom-modal-content">
|
<div class="custom-modal-content">
|
||||||
<n-card style="padding: 0 12px;">
|
<n-card style="padding: 0 12px">
|
||||||
<n-tabs
|
<n-tabs
|
||||||
type="line"
|
type="line"
|
||||||
@update:value="handleAddressBookTabChange"
|
@update:value="handleAddressBookTabChange"
|
||||||
tab-style="font-size: 16px; font-weight: 600;color: #8B8B8B;"
|
tab-style="font-size: 16px; font-weight: 600;color: #8B8B8B;"
|
||||||
>
|
>
|
||||||
|
<!-- <n-tab name="employeeAddressBook">组织架构</n-tab>
|
||||||
|
<n-tab name="employeeAddressBook">我的好友</n-tab> -->
|
||||||
|
<!-- <n-tab name="employeeAddressBook">组织架构</n-tab> -->
|
||||||
<n-tab name="employeeAddressBook">员工通讯录</n-tab>
|
<n-tab name="employeeAddressBook">员工通讯录</n-tab>
|
||||||
|
<!-- <n-tab name="myFriend">我的好友</n-tab> -->
|
||||||
<n-tab name="groupChatList">群聊列表</n-tab>
|
<n-tab name="groupChatList">群聊列表</n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<xSearchForm
|
<xSearchForm
|
||||||
@ -887,10 +1215,24 @@ const handleEnterSearchResultChat = () => {
|
|||||||
@change="changeGroupChatListSearch"
|
@change="changeGroupChatListSearch"
|
||||||
:cols="3"
|
:cols="3"
|
||||||
></xSearchForm>
|
></xSearchForm>
|
||||||
|
<xSearchForm
|
||||||
|
v-if="state.addressBookCurrentTab == 'myFriend'"
|
||||||
|
:search-config="state.myFriendSearchConfig"
|
||||||
|
customInputPlaceholder="请输入好友名称"
|
||||||
|
@change="changeMyFriendListSearch"
|
||||||
|
:cols="3"
|
||||||
|
></xSearchForm>
|
||||||
|
<p
|
||||||
|
v-if="state.addressBookCurrentTab === 'employeeAddressBook'"
|
||||||
|
style="transform: translateY(-10px)"
|
||||||
|
>
|
||||||
|
{{ state.company_name }}
|
||||||
|
</p>
|
||||||
<div
|
<div
|
||||||
class="addressBook-content"
|
class="addressBook-content"
|
||||||
v-if="state.addressBookCurrentTab == 'employeeAddressBook'"
|
v-if="state.addressBookCurrentTab == 'employeeAddressBook'"
|
||||||
>
|
>
|
||||||
|
<!-- 隐藏组织架构树 v-if="!state.addressBookSearchNickName && 0"-->
|
||||||
<div class="addressBook-tree" v-if="!state.addressBookSearchNickName">
|
<div class="addressBook-tree" v-if="!state.addressBookSearchNickName">
|
||||||
<fl-tree
|
<fl-tree
|
||||||
:data="state.treeData"
|
:data="state.treeData"
|
||||||
@ -926,6 +1268,36 @@ const handleEnterSearchResultChat = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 我的好友 -->
|
||||||
|
<div class="groupChatList-content" v-if="state.addressBookCurrentTab == 'myFriend'">
|
||||||
|
<div class="groupChatList-table">
|
||||||
|
<xNDataTable
|
||||||
|
:columns="state.myFriendListColumns"
|
||||||
|
:data="state.myFriendListData"
|
||||||
|
:style="{
|
||||||
|
height: '523px',
|
||||||
|
width: '1148px'
|
||||||
|
}"
|
||||||
|
flex-height
|
||||||
|
></xNDataTable>
|
||||||
|
<div class="groupChatList-pagination">
|
||||||
|
<n-pagination
|
||||||
|
v-model:page="state.myFriendListPage"
|
||||||
|
v-model:page-size="state.myFriendListPageSize"
|
||||||
|
:item-count="state.myFriendListTotal"
|
||||||
|
show-quick-jumper
|
||||||
|
show-size-picker
|
||||||
|
:page-sizes="[10, 20, 50]"
|
||||||
|
:on-update:page="handleMyFriendListPagination"
|
||||||
|
:on-update:page-size="handleMyFriendListPaginationSize"
|
||||||
|
>
|
||||||
|
<template #prefix="{ itemCount }"> 共 {{ itemCount }} 条记录 </template>
|
||||||
|
</n-pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="groupChatList-content" v-if="state.addressBookCurrentTab == 'groupChatList'">
|
<div class="groupChatList-content" v-if="state.addressBookCurrentTab == 'groupChatList'">
|
||||||
<div class="groupChatList-table">
|
<div class="groupChatList-table">
|
||||||
<xNDataTable
|
<xNDataTable
|
||||||
@ -957,6 +1329,41 @@ const handleEnterSearchResultChat = () => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</customModal>
|
</customModal>
|
||||||
|
<customModal
|
||||||
|
v-model:show="state.isShowAddFriendModal"
|
||||||
|
title="添加好友"
|
||||||
|
:style="state.customModalStyle"
|
||||||
|
:customCloseBtn="true"
|
||||||
|
:closable="false"
|
||||||
|
:customCloseEvent="true"
|
||||||
|
@customCloseModal="closeAddFriendModal"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div class="custom-modal-content">
|
||||||
|
<n-card style="padding: 0 12px">
|
||||||
|
<xSearchForm
|
||||||
|
:search-config="state.addFriendSearchConfig"
|
||||||
|
customInputPlaceholder="请输入姓名"
|
||||||
|
@change="changeAddFriendSearch"
|
||||||
|
:cols="3"
|
||||||
|
></xSearchForm>
|
||||||
|
<div class="groupChatList-content">
|
||||||
|
<div class="groupChatList-table">
|
||||||
|
<xNDataTable
|
||||||
|
:columns="state.addFriendListColumns"
|
||||||
|
:data="state.addFriendList"
|
||||||
|
:style="{
|
||||||
|
height: '523px',
|
||||||
|
width: '1148px'
|
||||||
|
}"
|
||||||
|
flex-height
|
||||||
|
></xNDataTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</customModal>
|
||||||
|
|
||||||
<customModal
|
<customModal
|
||||||
v-model:show="state.isShowSearchRecordModal"
|
v-model:show="state.isShowSearchRecordModal"
|
||||||
@ -969,7 +1376,7 @@ const handleEnterSearchResultChat = () => {
|
|||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="search-record-modal-content">
|
<div class="search-record-modal-content">
|
||||||
<n-card style="padding: 0 12px;">
|
<n-card style="padding: 0 12px">
|
||||||
<div class="search-record-input">
|
<div class="search-record-input">
|
||||||
<span class="search-record-input-title">搜索</span>
|
<span class="search-record-input-title">搜索</span>
|
||||||
<n-input
|
<n-input
|
||||||
|
@ -31,6 +31,7 @@ const onSingleForward = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onMultiDelete = () => {
|
const onMultiDelete = () => {
|
||||||
|
if(dialogueStore.selectItems.length>0){
|
||||||
confirmBox({
|
confirmBox({
|
||||||
content:'确定删除聊天记录',
|
content:'确定删除聊天记录',
|
||||||
confirmText:'删除'
|
confirmText:'删除'
|
||||||
@ -42,6 +43,10 @@ if (!msgIds.length) return
|
|||||||
dialogueStore.ApiDeleteRecord(msgIds)
|
dialogueStore.ApiDeleteRecord(msgIds)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
}else{
|
||||||
|
window['$message'].warning('请选择聊天记录')
|
||||||
|
}
|
||||||
|
|
||||||
// 批量删除
|
// 批量删除
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -59,7 +64,6 @@ const onContactModal = (data: { receiver_id: number; talk_type: number }[]) => {
|
|||||||
group_ids.push(o.receiver_id)
|
group_ids.push(o.receiver_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('user_ids',user_ids)
|
|
||||||
dialogueStore.ApiForwardRecord({
|
dialogueStore.ApiForwardRecord({
|
||||||
mode: forwardMode.value,
|
mode: forwardMode.value,
|
||||||
message_ids: msg_ids,
|
message_ids: msg_ids,
|
||||||
|
@ -3,7 +3,7 @@ import { watch, onMounted, ref, nextTick, onUnmounted } from 'vue'
|
|||||||
import { NDropdown, NCheckbox, NPopover, NInfiniteScroll } from 'naive-ui'
|
import { NDropdown, NCheckbox, NPopover, NInfiniteScroll } from 'naive-ui'
|
||||||
import { Loading, MoreThree, ToTop } from '@icon-park/vue-next'
|
import { Loading, MoreThree, ToTop } from '@icon-park/vue-next'
|
||||||
import { bus } from '@/utils/event-bus'
|
import { bus } from '@/utils/event-bus'
|
||||||
import { useDialogueStore } from '@/store'
|
import { useDialogueStore, useTalkStore } from '@/store'
|
||||||
import { formatTime, parseTime } from '@/utils/datetime'
|
import { formatTime, parseTime } from '@/utils/datetime'
|
||||||
import { clipboard, htmlDecode, clipboardImage } from '@/utils/common'
|
import { clipboard, htmlDecode, clipboardImage } from '@/utils/common'
|
||||||
import { downloadImage } from '@/utils/functions'
|
import { downloadImage } from '@/utils/functions'
|
||||||
@ -19,8 +19,11 @@ import RevokeMessage from '@/components/talk/message/RevokeMessage.vue'
|
|||||||
import { voiceToText, ServeMessageReadDetail } from '@/api/chat.js'
|
import { voiceToText, ServeMessageReadDetail } from '@/api/chat.js'
|
||||||
import { confirmBox } from '@/components/confirm-box/service.js'
|
import { confirmBox } from '@/components/confirm-box/service.js'
|
||||||
import ws from '@/connect'
|
import ws from '@/connect'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import avatarModule from '@/components/avatar-module/index.vue'
|
import avatarModule from '@/components/avatar-module/index.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 定义消息已读状态接口
|
// 定义消息已读状态接口
|
||||||
interface ReadStatus {
|
interface ReadStatus {
|
||||||
msg_ids: string[]
|
msg_ids: string[]
|
||||||
@ -82,15 +85,32 @@ const { loadConfig, records, onLoad, onRefreshLoad, onJumpMessage, onLoadMoreDow
|
|||||||
)
|
)
|
||||||
const uploadsStore = useUploadsStore()
|
const uploadsStore = useUploadsStore()
|
||||||
const { useMessage } = useUtil()
|
const { useMessage } = useUtil()
|
||||||
const { dropdown, showDropdownMenu, closeDropdownMenu } = useMenu()
|
const { dropdown, showDropdownMenu, closeDropdownMenu, isOneMonthBefore } = useMenu()
|
||||||
const { showUserInfoModal } = useInject()
|
const { showUserInfoModal } = useInject()
|
||||||
const dialogueStore = useDialogueStore()
|
const dialogueStore = useDialogueStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const talkStore = useTalkStore()
|
||||||
// const showUserInfoModal = (uid: number) => {
|
// const showUserInfoModal = (uid: number) => {
|
||||||
// userStore.getUserInfo(uid)
|
// userStore.getUserInfo(uid)
|
||||||
// }
|
// }
|
||||||
// 置底按钮
|
// 置底按钮
|
||||||
const skipBottom = ref(false)
|
const skipBottom = ref(false)
|
||||||
|
const goToMessage = (result) => {
|
||||||
|
const talk_type = props.talk_type
|
||||||
|
const receiver_id = props.receiver_id
|
||||||
|
dialogueStore.specifiedMsg = encodeURIComponent(
|
||||||
|
JSON.stringify({
|
||||||
|
talk_type,
|
||||||
|
receiver_id,
|
||||||
|
msg_id: result.msg_id,
|
||||||
|
cursor: result.sequence - 15 > 0 ? result.sequence - 15 : 0,
|
||||||
|
direction: 'down',
|
||||||
|
sort_sequence: 'asc',
|
||||||
|
create_time: result.created_at
|
||||||
|
})
|
||||||
|
)
|
||||||
|
talkStore.toTalk(talk_type, receiver_id, router)
|
||||||
|
}
|
||||||
// 是否显示消息时间
|
// 是否显示消息时间
|
||||||
const isShowTalkTime = (index: number, datetime: string) => {
|
const isShowTalkTime = (index: number, datetime: string) => {
|
||||||
if (datetime == undefined) {
|
if (datetime == undefined) {
|
||||||
@ -184,7 +204,7 @@ const onCopyText = (data: ITalkRecord) => {
|
|||||||
return clipboard(htmlDecode(data.extra.content), () => useMessage.success('复制成功'))
|
return clipboard(htmlDecode(data.extra.content), () => useMessage.success('复制成功'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log('data.extra?.url', data.extra?.url)
|
||||||
if (data.extra?.url) {
|
if (data.extra?.url) {
|
||||||
return clipboardImage(data.extra.url, () => {
|
return clipboardImage(data.extra.url, () => {
|
||||||
useMessage.success('复制成功')
|
useMessage.success('复制成功')
|
||||||
@ -331,6 +351,10 @@ const onContextMenuHandle = (key: string) => {
|
|||||||
|
|
||||||
const onRowClick = (item: ITalkRecord) => {
|
const onRowClick = (item: ITalkRecord) => {
|
||||||
if (dialogueStore.isOpenMultiSelect) {
|
if (dialogueStore.isOpenMultiSelect) {
|
||||||
|
if (!isOneMonthBefore(item.created_at.split(' ')[0])) {
|
||||||
|
return useMessage.info('只支持转发近一个月内的消息')
|
||||||
|
}
|
||||||
|
console.log('item.msg_type', item.msg_type)
|
||||||
if (ForwardableMessageType.includes(item.msg_type)) {
|
if (ForwardableMessageType.includes(item.msg_type)) {
|
||||||
item.isCheck = !item.isCheck
|
item.isCheck = !item.isCheck
|
||||||
} else {
|
} else {
|
||||||
@ -348,6 +372,7 @@ let noRefreshTimer: number | null = null
|
|||||||
watch(
|
watch(
|
||||||
() => props,
|
() => props,
|
||||||
async (newProps) => {
|
async (newProps) => {
|
||||||
|
console.log('监听props',newProps)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
// 生成当前会话的唯一标识
|
// 生成当前会话的唯一标识
|
||||||
const newSessionKey = `${newProps.talk_type}_${newProps.receiver_id}`
|
const newSessionKey = `${newProps.talk_type}_${newProps.receiver_id}`
|
||||||
@ -390,7 +415,7 @@ watch(
|
|||||||
}, 3000)
|
}, 3000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.log('fsd付大夫')
|
||||||
onLoad(
|
onLoad(
|
||||||
{
|
{
|
||||||
receiver_id: newProps.receiver_id,
|
receiver_id: newProps.receiver_id,
|
||||||
@ -400,7 +425,7 @@ watch(
|
|||||||
specialParams ? { specifiedMsg: specialParams } : undefined
|
specialParams ? { specifiedMsg: specialParams } : undefined
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true }
|
{ deep: true,immediate:true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// onMounted(() => {
|
// onMounted(() => {
|
||||||
@ -531,7 +556,6 @@ const checkVisibleOutElements = () => {
|
|||||||
})
|
})
|
||||||
if (waitDoCheck.length > 0) {
|
if (waitDoCheck.length > 0) {
|
||||||
waitDoCheck.forEach((doCheckItem) => {
|
waitDoCheck.forEach((doCheckItem) => {
|
||||||
console.error('====组装了新版已读回执参数,需要发送socket=====', doCheckItem)
|
|
||||||
ws.emit('im.message.listen.read', doCheckItem)
|
ws.emit('im.message.listen.read', doCheckItem)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -591,7 +615,6 @@ watch(
|
|||||||
if (observer) {
|
if (observer) {
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新初始化观察者
|
// 重新初始化观察者
|
||||||
const options = {
|
const options = {
|
||||||
root: null,
|
root: null,
|
||||||
@ -599,7 +622,6 @@ watch(
|
|||||||
rootMargin: '50px 0px'
|
rootMargin: '50px 0px'
|
||||||
}
|
}
|
||||||
observer = new IntersectionObserver(handleIntersection, options)
|
observer = new IntersectionObserver(handleIntersection, options)
|
||||||
|
|
||||||
// 重新观察所有消息元素
|
// 重新观察所有消息元素
|
||||||
const messageElements = document.querySelectorAll('.message-item')
|
const messageElements = document.querySelectorAll('.message-item')
|
||||||
messageElements.forEach((el) => {
|
messageElements.forEach((el) => {
|
||||||
@ -767,7 +789,7 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
<div class="load-toolbar pointer">
|
<div class="load-toolbar pointer">
|
||||||
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
|
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
|
||||||
<span v-else-if="loadConfig.status == 1" @click="onRefreshLoad"> 查看更多消息 ... </span>
|
<span v-else-if="loadConfig.status == 1" @click="onRefreshLoad"> 查看更多消息 ... </span>
|
||||||
<span v-else class="no-more"> 没有更多消息了 </span>
|
<span v-else-if="loadConfig.status == 2 || loadConfig.status == 3" class="no-more"> 没有更多消息了 </span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -815,8 +837,10 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
>
|
>
|
||||||
<!-- 多选按钮 -->
|
<!-- 多选按钮 -->
|
||||||
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column shrink-0">
|
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column shrink-0">
|
||||||
|
<!-- 近一个月外的消息多选框禁用 {{ item }} -->
|
||||||
<n-checkbox
|
<n-checkbox
|
||||||
size="small"
|
size="small"
|
||||||
|
:disabled="!isOneMonthBefore(item.created_at.split(' ')[0])"
|
||||||
:checked="item.isCheck"
|
:checked="item.isCheck"
|
||||||
@update:checked="item.isCheck = !item.isCheck"
|
@update:checked="item.isCheck = !item.isCheck"
|
||||||
/>
|
/>
|
||||||
@ -854,7 +878,14 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="talk-content"
|
class="talk-content"
|
||||||
:class="{ pointer: dialogueStore.isOpenMultiSelect }"
|
:class="{
|
||||||
|
pointer:
|
||||||
|
dialogueStore.isOpenMultiSelect &&
|
||||||
|
isOneMonthBefore(item.created_at.split(' ')[0]),
|
||||||
|
'cursor-not-allowed':
|
||||||
|
dialogueStore.isOpenMultiSelect &&
|
||||||
|
!isOneMonthBefore(item.created_at.split(' ')[0])
|
||||||
|
}"
|
||||||
@click="onRowClick(item)"
|
@click="onRowClick(item)"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
@ -871,7 +902,7 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
"
|
"
|
||||||
class="mr-10px"
|
class="mr-10px"
|
||||||
>
|
>
|
||||||
<n-button text style="font-size: 20px;" @click="retry(item)">
|
<n-button text style="font-size: 20px" @click="retry(item)">
|
||||||
<n-icon color="#CF3050">
|
<n-icon color="#CF3050">
|
||||||
<ExclamationCircleFilled />
|
<ExclamationCircleFilled />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@ -895,11 +926,11 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
<n-icon class="more-tools pointer" :component="MoreThree" @click="onContextMenu($event, item)" />
|
<n-icon class="more-tools pointer" :component="MoreThree" @click="onContextMenu($event, item)" />
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
<!-- @click="onJumpMessage(item.extra?.reply?.msg_id)" -->
|
||||||
<div
|
<div
|
||||||
v-if="item.extra.reply"
|
v-if="item.extra.reply"
|
||||||
class="talk-reply pointer"
|
class="talk-reply pointer"
|
||||||
@click="onJumpMessage(item.extra?.reply?.msg_id)"
|
@click="goToMessage(item.extra?.reply)"
|
||||||
>
|
>
|
||||||
<n-icon :component="ToTop" size="14" class="icon-top" />
|
<n-icon :component="ToTop" size="14" class="icon-top" />
|
||||||
<span class="ellipsis">
|
<span class="ellipsis">
|
||||||
@ -916,14 +947,14 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
<n-popover
|
<n-popover
|
||||||
trigger="click"
|
trigger="click"
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
style="height: 382px; padding: 0;"
|
style="height: 382px; padding: 0"
|
||||||
v-if="props.talk_type === 2"
|
v-if="props.talk_type === 2"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<span
|
<span
|
||||||
v-if="props.talk_type === 2"
|
v-if="props.talk_type === 2"
|
||||||
@click="toShowMessageReadDetail(item)"
|
@click="toShowMessageReadDetail(item)"
|
||||||
style="cursor: pointer;"
|
style="cursor: pointer"
|
||||||
>
|
>
|
||||||
已读 ({{ item?.read_total_num || 0 }}/{{
|
已读 ({{ item?.read_total_num || 0 }}/{{
|
||||||
props.num - 1 > 0 ? props.num - 1 : 0
|
props.num - 1 > 0 ? props.num - 1 : 0
|
||||||
@ -945,11 +976,12 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
</n-tab>
|
</n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<div class="talk-read-list">
|
<div class="talk-read-list">
|
||||||
<n-infinite-scroll style="height: 340px;" @load="loadMoreReadListDetail">
|
<n-infinite-scroll style="height: 340px" @load="loadMoreReadListDetail">
|
||||||
<div
|
<div
|
||||||
class="talk-read-list-item"
|
class="talk-read-list-item"
|
||||||
v-for="(talkReadDetailItem,
|
v-for="(
|
||||||
talkReadDetailIndex) in state.talkReadListDetail"
|
talkReadDetailItem, talkReadDetailIndex
|
||||||
|
) in state.talkReadListDetail"
|
||||||
:key="talkReadDetailIndex"
|
:key="talkReadDetailIndex"
|
||||||
>
|
>
|
||||||
<avatarModule
|
<avatarModule
|
||||||
@ -969,10 +1001,10 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
}"
|
}"
|
||||||
></avatarModule>
|
></avatarModule>
|
||||||
<div class="talk-read-list-item-info">
|
<div class="talk-read-list-item-info">
|
||||||
<span style="font-size: 12px; font-weight: 600; line-height: 17px;">{{
|
<span style="font-size: 12px; font-weight: 600; line-height: 17px">{{
|
||||||
talkReadDetailItem.nickName
|
talkReadDetailItem.nickName
|
||||||
}}</span>
|
}}</span>
|
||||||
<span style="font-size: 12px; color: #999; line-height: 14px;">{{
|
<span style="font-size: 12px; color: #999; line-height: 14px">{{
|
||||||
talkReadDetailItem.jobNum
|
talkReadDetailItem.jobNum
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -1004,7 +1036,7 @@ const onCustomSkipBottomEvent = () => {
|
|||||||
:show="dropdown.show"
|
:show="dropdown.show"
|
||||||
:x="dropdown.x"
|
:x="dropdown.x"
|
||||||
:y="dropdown.y"
|
:y="dropdown.y"
|
||||||
style="width: 142px;"
|
style="width: 142px"
|
||||||
:options="dropdown.options"
|
:options="dropdown.options"
|
||||||
@select="onContextMenuHandle"
|
@select="onContextMenuHandle"
|
||||||
@clickoutside="closeDropdownMenu"
|
@clickoutside="closeDropdownMenu"
|
||||||
|
@ -101,10 +101,13 @@ const onSendImageEvent = ({ data, callBack }) => {
|
|||||||
|
|
||||||
// 发送视频消息
|
// 发送视频消息
|
||||||
const onSendVideoEvent = async ({ data }) => {
|
const onSendVideoEvent = async ({ data }) => {
|
||||||
|
|
||||||
|
|
||||||
// 获取视频首帧作为封面图
|
// 获取视频首帧作为封面图
|
||||||
// let resp = await getVideoImage(data)
|
let videoPreview = null
|
||||||
|
try {
|
||||||
|
videoPreview = await getVideoImage(data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取视频封面失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
// 先创建一个带有上传ID的临时消息对象,用于显示进度
|
// 先创建一个带有上传ID的临时消息对象,用于显示进度
|
||||||
const uploadId = `video-${Date.now()}-${Math.floor(Math.random() * 1000)}`
|
const uploadId = `video-${Date.now()}-${Math.floor(Math.random() * 1000)}`
|
||||||
@ -112,6 +115,9 @@ const onSendVideoEvent = async ({ data }) => {
|
|||||||
// 创建临时消息记录
|
// 创建临时消息记录
|
||||||
const tempMessage = {
|
const tempMessage = {
|
||||||
msg_id: uploadId,
|
msg_id: uploadId,
|
||||||
|
insert_sequence: dialogueStore.records.length > 0
|
||||||
|
? dialogueStore.records[dialogueStore.records.length-1].sequence
|
||||||
|
: 0,
|
||||||
sequence: Date.now(),
|
sequence: Date.now(),
|
||||||
talk_type: props.talk_type,
|
talk_type: props.talk_type,
|
||||||
msg_type: 5, // 视频消息类型
|
msg_type: 5, // 视频消息类型
|
||||||
@ -123,7 +129,7 @@ const onSendVideoEvent = async ({ data }) => {
|
|||||||
content: '',
|
content: '',
|
||||||
created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'),
|
created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'),
|
||||||
extra: {
|
extra: {
|
||||||
url: '',
|
url: videoPreview ? URL.createObjectURL(data) : '', // 使用本地视频URL作为预览
|
||||||
size: data.size,
|
size: data.size,
|
||||||
is_uploading: true,
|
is_uploading: true,
|
||||||
upload_id: uploadId,
|
upload_id: uploadId,
|
||||||
@ -134,8 +140,8 @@ const onSendVideoEvent = async ({ data }) => {
|
|||||||
float: 'right' // 我发送的消息显示在右侧
|
float: 'right' // 我发送的消息显示在右侧
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接添加到对话记录中
|
// 使用新的方法添加上传任务
|
||||||
dialogueStore.addDialogueRecord(tempMessage)
|
dialogueStore.addUploadTask(tempMessage)
|
||||||
nextTick(()=>{
|
nextTick(()=>{
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
})
|
})
|
||||||
@ -149,7 +155,6 @@ const onSendVideoEvent = async ({ data }) => {
|
|||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
dialogueStore.batchDelDialogueRecord([uploadId])
|
dialogueStore.batchDelDialogueRecord([uploadId])
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -161,13 +166,12 @@ const onSendCodeEvent = ({ data, callBack }) => {
|
|||||||
|
|
||||||
// 发送文件消息
|
// 发送文件消息
|
||||||
const onSendFileEvent = ({ data }) => {
|
const onSendFileEvent = ({ data }) => {
|
||||||
let maxsize = 200 * 1024 * 1024
|
|
||||||
if (data.size > maxsize) {
|
|
||||||
return window['$message'].warning('上传文件不能超过100M!')
|
|
||||||
}
|
|
||||||
const clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}`
|
const clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}`
|
||||||
const tempMessage = {
|
const tempMessage = {
|
||||||
msg_id: clientUploadId,
|
msg_id: clientUploadId,
|
||||||
|
insert_sequence: dialogueStore.records.length > 0
|
||||||
|
? dialogueStore.records[dialogueStore.records.length-1].sequence
|
||||||
|
: 0,
|
||||||
sequence: Date.now(),
|
sequence: Date.now(),
|
||||||
talk_type: props.talk_type,
|
talk_type: props.talk_type,
|
||||||
msg_type: 6,
|
msg_type: 6,
|
||||||
@ -189,7 +193,7 @@ const onSendFileEvent = ({ data }) => {
|
|||||||
},
|
},
|
||||||
float: 'right'
|
float: 'right'
|
||||||
}
|
}
|
||||||
dialogueStore.addDialogueRecord(tempMessage)
|
dialogueStore.addUploadTask(tempMessage)
|
||||||
nextTick(()=>{
|
nextTick(()=>{
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
})
|
})
|
||||||
@ -198,8 +202,8 @@ const onSendFileEvent = ({ data }) => {
|
|||||||
dialogueStore.updateUploadProgress(clientUploadId, percentage)
|
dialogueStore.updateUploadProgress(clientUploadId, percentage)
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
dialogueStore.batchDelDialogueRecord([clientUploadId])
|
// 上传完成后,上传任务已经被removeUploadTask方法移除
|
||||||
|
// 不需要再次从records中删除
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import { useDialogueStore } from '@/store/modules/dialogue.js'
|
import { useDialogueStore } from '@/store/modules/dialogue.js'
|
||||||
|
|
||||||
interface IDropdown {
|
interface IDropdown {
|
||||||
@ -9,16 +10,34 @@ interface IDropdown {
|
|||||||
item: any
|
item: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const isRevoke = (uid: any, item: any): boolean => {
|
const isRevoke = (uid: number, item: any): boolean => {
|
||||||
if (uid != item.user_id) {
|
// 不是自己发的消息不能撤回
|
||||||
return false
|
if (uid !== item.user_id) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const datetime = item.created_at.replace(/-/g, '/')
|
// 检查消息是否在撤回时间限制内(5分钟)
|
||||||
|
const messageTime = dayjs(item.created_at);
|
||||||
const time = new Date().getTime() - Date.parse(datetime)
|
const now = dayjs();
|
||||||
|
const diffInMinutes = now.diff(messageTime, 'minute');
|
||||||
return Math.floor(time / 1000 / 60) <= 2
|
return diffInMinutes <= 5;
|
||||||
|
}
|
||||||
|
// 判断是否可以添加撤回选项的函数
|
||||||
|
const canAddRevokeOption = (uid: number, item: any, isManager: boolean): boolean => {
|
||||||
|
// 单聊情况:自己发的且在时间限制内
|
||||||
|
if (item.talk_type === 1) {
|
||||||
|
return isRevoke(uid, item) && item.float === 'right';
|
||||||
|
}
|
||||||
|
// 群聊情况
|
||||||
|
else if (item.talk_type === 2) {
|
||||||
|
// 管理员可以撤回任何消息
|
||||||
|
if (isManager) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 普通成员只能撤回自己的且在时间限制内的消息
|
||||||
|
return isRevoke(uid, item) && item.float === 'right';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
const dialogueStore = useDialogueStore()
|
const dialogueStore = useDialogueStore()
|
||||||
export function useMenu() {
|
export function useMenu() {
|
||||||
@ -29,10 +48,17 @@ export function useMenu() {
|
|||||||
y: 0,
|
y: 0,
|
||||||
item: {}
|
item: {}
|
||||||
})
|
})
|
||||||
|
// 判断时间是否超过一个月
|
||||||
|
function isOneMonthBefore(date) {
|
||||||
|
const oneMonthAgo = new Date()
|
||||||
|
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1)
|
||||||
|
const inputDate = new Date(date)
|
||||||
|
return !(inputDate <= oneMonthAgo)
|
||||||
|
}
|
||||||
const showDropdownMenu = (e: any, uid: number, item: any) => {
|
const showDropdownMenu = (e: any, uid: number, item: any) => {
|
||||||
// dropdown.item = Object.assign({}, item)
|
// dropdown.item = Object.assign({}, item)
|
||||||
dropdown.item = item
|
dropdown.item = item
|
||||||
|
dropdown.item.is_self_action = true
|
||||||
dropdown.options = []
|
dropdown.options = []
|
||||||
if ([4].includes(item.msg_type)) {
|
if ([4].includes(item.msg_type)) {
|
||||||
if (item.is_convert_text === 1) {
|
if (item.is_convert_text === 1) {
|
||||||
@ -40,21 +66,22 @@ export function useMenu() {
|
|||||||
} else {
|
} else {
|
||||||
dropdown.options.push({ label: '转文字', key: 'convertText' })
|
dropdown.options.push({ label: '转文字', key: 'convertText' })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if ([1, 3].includes(item.msg_type)) {
|
if ([1, 3].includes(item.msg_type)) {
|
||||||
dropdown.options.push({ label: '复制', key: 'copy' })
|
dropdown.options.push({ label: '复制', key: 'copy' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOneMonthBefore(new Date(item.created_at.split(' ')[0]))) {
|
||||||
|
// 根据时间判断只有近一个月内的消息才能支持多选
|
||||||
dropdown.options.push({ label: '多选', key: 'multiSelect' })
|
dropdown.options.push({ label: '多选', key: 'multiSelect' })
|
||||||
dropdown.options.push({ label: '引用', key: 'quote' })
|
|
||||||
if (isRevoke(uid, item)|| (dialogueStore.groupInfo as any).is_manager) {
|
|
||||||
dropdown.options.push({ label: `撤回`, key: 'revoke' })
|
|
||||||
}
|
}
|
||||||
|
dropdown.options.push({ label: '引用', key: 'quote' })
|
||||||
|
if (canAddRevokeOption(uid, item, (dialogueStore.groupInfo as any).is_manager)) {
|
||||||
|
dropdown.options.push({ label: '撤回', key: 'revoke' });
|
||||||
|
}
|
||||||
|
|
||||||
dropdown.options.push({ label: '删除', key: 'delete' })
|
dropdown.options.push({ label: '删除', key: 'delete' })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// if ([3, 4, 5].includes(item.msg_type)) {
|
// if ([3, 4, 5].includes(item.msg_type)) {
|
||||||
// dropdown.options.push({ label: '下载', key: 'download' })
|
// dropdown.options.push({ label: '下载', key: 'download' })
|
||||||
// }
|
// }
|
||||||
@ -63,7 +90,6 @@ export function useMenu() {
|
|||||||
// dropdown.options.push({ label: '收藏', key: 'collect' })
|
// dropdown.options.push({ label: '收藏', key: 'collect' })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
dropdown.x = e.clientX
|
dropdown.x = e.clientX
|
||||||
dropdown.y = e.clientY
|
dropdown.y = e.clientY
|
||||||
dropdown.show = true
|
dropdown.show = true
|
||||||
@ -74,5 +100,5 @@ export function useMenu() {
|
|||||||
dropdown.item = {}
|
dropdown.item = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { dropdown, showDropdownMenu, closeDropdownMenu }
|
return { dropdown, showDropdownMenu, closeDropdownMenu, isOneMonthBefore }
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,6 @@ const config = {
|
|||||||
},
|
},
|
||||||
documentType,
|
documentType,
|
||||||
editorConfig: {
|
editorConfig: {
|
||||||
|
|
||||||
mode: 'view',
|
mode: 'view',
|
||||||
lang: 'zh-CN',
|
lang: 'zh-CN',
|
||||||
user: {
|
user: {
|
||||||
|
Loading…
Reference in New Issue
Block a user