Compare commits

..

33 Commits
main ... xingyy

Author SHA1 Message Date
Phoenix
1a85e9d13e 编辑器优化 2025-06-11 16:54:54 +08:00
Phoenix
bab907a1e2 fix: 修复未读消息数量显示重复问题并移除调试日志
修复TalkItem.vue中未读消息数量显示重复的问题
移除MultiSelectFooter.vue中无用的调试日志打印
2025-06-11 15:07:20 +08:00
Phoenix
45e4415cec fix: 修复消息转发、上传和编辑器引用删除功能
- 添加ChatMsgTypeForward到可转发消息类型
- 修复请求拦截器中状态码判断逻辑
- 优化视频消息上传封面获取和预览显示
- 修复上传分片错误处理和进度更新
- 重构编辑器引用删除逻辑,提升代码可维护性
- 调整图片消息样式和上传蒙版显示
2025-06-11 14:47:13 +08:00
Phoenix
57e4ba69d9 refactor: 统一错误消息处理并优化编辑器功能
- 将错误消息处理移至请求拦截器统一处理
- 优化编辑器提及功能,过滤当前用户
- 清理编辑器相关冗余代码和注释
- 改进空消息检测逻辑
2025-06-11 11:39:11 +08:00
Phoenix
88bbf16699 fix(editor): 修复编辑器空内容判断和换行处理问题
改进编辑器空内容检测逻辑,确保更准确地判断是否为空内容
重构换行处理逻辑,使用辅助函数插入换行符并保持光标位置
优化消息发送前的空内容检查,防止发送无效消息
2025-06-11 11:20:15 +08:00
Phoenix
d46ced7614 style(消息组件): 移除系统消息的不可选中和悬停样式
移除系统消息组件中不必要的 user-select 属性和链接的悬停效果,保持样式简洁
2025-06-11 10:25:59 +08:00
Phoenix
044617580c fix(群组消息): 移除用户信息弹窗的点击事件
移除多个群组系统消息组件中用户名的点击事件,这些事件原本会触发用户信息弹窗
2025-06-11 10:23:42 +08:00
Phoenix
54a46e2fb4 refactor(editor): 优化编辑器代码结构并清理注释 2025-06-11 09:51:16 +08:00
Phoenix
28938aba66 refactor(消息面板): 重构消息撤回逻辑,提取公共函数
将消息撤回的条件判断逻辑提取为独立函数 canAddRevokeOption
简化主逻辑代码,提高可读性和可维护性
2025-06-11 09:44:17 +08:00
Phoenix
8e645226b8 fix(消息面板): 添加普通用户撤回消息的条件限制
当用户不是管理员时,只有在撤回时间限制内且消息是自己发送的情况下才显示撤回选项
2025-06-11 09:39:49 +08:00
Phoenix
4b5c160e94 Merge branch 'dev' of http://172.16.100.91:3000/scout666/chat-pc into dev 2025-06-10 15:03:31 +08:00
Phoenix
ebd567a757 fix(消息面板): 修复消息菜单和撤回消息按钮的显示逻辑
修复消息菜单中缺少的is_self_action属性设置,确保撤回消息按钮仅在自身操作时显示
调整编辑器内容处理逻辑,优化草稿保存的数据结构
2025-06-10 15:03:29 +08:00
18871db6b6 Merge branch 'main' into dev 2025-06-10 14:56:10 +08:00
Phoenix
1ae317dbb3 Merge branch 'xingyy' into dev 2025-06-10 13:39:29 +08:00
Phoenix
e4354d42cd feat(消息面板): 添加dayjs依赖并优化消息撤回时间计算
使用dayjs替换原有的日期处理逻辑,提高代码可读性并延长消息撤回时间至5分钟
2025-06-10 13:28:54 +08:00
Phoenix
8bba2d64af fix(editor): 优化提及插入逻辑并修复光标位置问题
重构提及插入逻辑,使用更直接的方式删除@符号到光标间的内容
将普通空格替换为不间断空格以避免被HTML压缩
确保光标始终正确放置在插入内容之后
2025-06-10 11:03:24 +08:00
Phoenix
d4e52152ef feat(editor): 添加鼠标点击选择mention功能并优化插入逻辑
- 新增handleMentionSelectByMouse函数处理鼠标点击选择mention
- 重构insertMention函数,支持传入range参数并优化插入逻辑
- 修复mention列表点击事件,防止默认行为导致的问题
- 优化onSubscribeMention函数,确保焦点和选区正确处理
2025-06-10 09:45:10 +08:00
Phoenix
bdf07155c8 fix(editor): 修复提及功能中用户ID处理问题
修复提及成员时用户ID类型转换问题,确保ID统一为字符串类型。同时为管理员添加"全体成员"提及选项,并完善提及列表的数据处理逻辑。
2025-06-09 16:48:52 +08:00
Phoenix
b905db0cfa fix: 优化消息撤回逻辑和编辑器内容处理
- 调整消息菜单的撤回选项显示逻辑,区分单聊和群聊场景
- 修复编辑器内容处理,使用trimEnd替代trim避免尾部空格问题
- 移除重复的quote元素删除操作
- 优化编辑器空内容判断逻辑
2025-06-09 15:29:24 +08:00
Phoenix
3b6d998ce1 Merge branch 'xingyy' into dev 2025-06-09 14:46:59 +08:00
Phoenix
5340461a7e fix(utils): 修复wujie环境下剪贴板功能兼容性问题
修改clipboardImage方法以支持wujie微前端环境,使用主应用的navigator.clipboard对象
同时优化canvas图片绘制参数,确保图片缩放正确
2025-06-09 14:46:33 +08:00
Phoenix
45eec2ff22 Merge branch 'dev' of http://172.16.100.91:3000/scout666/chat-pc into dev 2025-06-09 13:57:38 +08:00
Phoenix
9c34066128 Merge branch 'xingyy' into dev 2025-06-09 13:57:32 +08:00
Phoenix
628894a254 refactor(editor): 优化mention处理逻辑并移除调试日志
移除调试用的console.log语句
重构mention列表过滤逻辑,使用startsWith替代includes
添加Backspace和Delete键删除mention元素的功能
优化键盘事件处理逻辑,减少不必要的DOM操作
2025-06-09 13:57:15 +08:00
92fce58429 Merge branch 'main' into dev 2025-06-09 13:33:02 +08:00
Phoenix
2e998a1174 Merge branch 'dev' of http://172.16.100.91:3000/scout666/chat-pc into dev 2025-06-09 11:51:48 +08:00
Phoenix
60a2fb996b Merge branch 'xingyy' into dev 2025-06-09 11:51:38 +08:00
b282562cdd Merge branch 'main' into dev 2025-06-06 18:54:59 +08:00
d0abf7d8ab Merge branch 'main' into dev 2025-06-06 09:05:56 +08:00
Phoenix
409af72039 Merge branch 'dev' of http://172.16.100.91:3000/scout666/chat-pc into dev 2025-06-05 14:14:47 +08:00
Phoenix
799599bd83 Merge branch 'xingyy' into dev 2025-06-05 14:14:38 +08:00
ec18d85546 Merge branch 'wyfMain-dev' into dev 2025-06-05 09:20:27 +08:00
Phoenix
a97f293a6c Merge branch 'xingyy' into dev
# Conflicts:
#	src/views/message/inner/panel/PanelFooter.vue   resolved by xingyy version
2025-06-04 16:32:24 +08:00
41 changed files with 1345 additions and 1253 deletions

View File

@ -26,6 +26,7 @@
"@vueuse/core": "^10.7.0", "@vueuse/core": "^10.7.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",
"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",

View File

@ -44,6 +44,9 @@ importers:
axios: axios:
specifier: ^1.6.2 specifier: ^1.6.2
version: 1.9.0 version: 1.9.0
dayjs:
specifier: ^1.11.13
version: 1.11.13
highlight.js: highlight.js:
specifier: ^11.5.0 specifier: ^11.5.0
version: 11.11.1 version: 11.11.1

File diff suppressed because it is too large Load Diff

View File

@ -686,18 +686,13 @@ const fileTypeAvatar = (fileType) => {
const previewPDF = (item) => { const previewPDF = (item) => {
console.log(item) console.log(item)
// if (typeof plus !== 'undefined') { if (typeof plus !== 'undefined') {
// downloadAndOpenFile(item) downloadAndOpenFile(item)
// } else { } else {
// document.addEventListener('plusready', () => { document.addEventListener('plusready', () => {
// downloadAndOpenFile(item) downloadAndOpenFile(item)
// }) })
// } }
window.open(
`${import.meta.env.VITE_PAGE_URL}/office?url=${item.extra.path}`,
'_blank',
'width=1200,height=900,left=200,top=200,toolbar=no,menubar=no,scrollbars=yes,resizable=yes,location=no,status=no'
)
} }
const downloadAndOpenFile = (item) => { const downloadAndOpenFile = (item) => {
@ -931,6 +926,7 @@ body:deep(.round-3) {
} }
.condition-each-resultList { .condition-each-resultList {
.condition-each-resultList-each { .condition-each-resultList-each {
border-bottom: 1px solid #f8f8f8;
.condition-each-result-main { .condition-each-result-main {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -946,16 +942,9 @@ body:deep(.round-3) {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
padding: 14px 20px; padding: 14px 0;
// background-color: #f3f3f3; // background-color: #f3f3f3;
border-radius: 4px; border-radius: 4px;
cursor: pointer;
border-bottom: 1px solid #f8f8f8;
&:hover {
background-color: rgba(70, 41, 157, 0.1)
}
.attachment-avatar { .attachment-avatar {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -1137,10 +1126,6 @@ body:deep(.round-3) {
.image-container { .image-container {
width: 100% !important; width: 100% !important;
height: 100% !important; height: 100% !important;
&:hover {
cursor: pointer;
border: 1px solid #46299d;
}
} }
:deep(.n-image) { :deep(.n-image) {

View File

@ -69,43 +69,7 @@
class="text-[12px] font-regular" class="text-[12px] font-regular"
:text="resultDetail" :text="resultDetail"
:searchText="props.searchText" :searchText="props.searchText"
v-if="props.searchItem?.msg_type !== 3 && props.searchItem?.msg_type !== 6"
/> />
<div class="message-component-wrapper" v-if="props.searchItem?.msg_type === 3" @click.stop>
<component
:is="MessageComponents[props.searchItem?.msg_type] || 'unknown-message'"
:extra="resultDetail"
:data="props?.searchItem"
/>
</div>
<div class="file-message-wrapper" v-if="props.searchItem?.msg_type === 6" @click.stop>
<div class="condition-each-result-attachments" @click="previewPDF(resultDetail.path)">
<div class="attachment-avatar">
<img :src="resultDetail?.file_avatar" />
</div>
<div class="attachment-info">
<div class="attachment-info-title">
<span class="text-[14px] font-regular">
{{ resultDetail?.name }}
</span>
<span
class="text-[14px] font-regular"
style="color: #999999; flex-shrink: 0; margin: 0 0 0 20px;"
>
{{ resultDetail?.dateTime }}
</span>
</div>
<div class="attachment-sub-info">
<span class="text-[12px] font-regular">
{{ resultDetail?.typeText }}
</span>
<span class="text-[12px] font-regular" style="flex-shrink: 0; margin: 0 0 0 20px;">
{{ resultDetail?.fileSize }}
</span>
</div>
</div>
</div>
</div>
<div class="searchRecordDetail-fastLocal" v-if="searchRecordDetail"> <div class="searchRecordDetail-fastLocal" v-if="searchRecordDetail">
<span>定位到聊天位置</span> <span>定位到聊天位置</span>
</div> </div>
@ -121,7 +85,7 @@ import avatarModule from '@/components/avatar-module/index.vue'
import { ref, watch, computed, onMounted, onUnmounted, reactive, defineProps } from 'vue' import { ref, watch, computed, onMounted, onUnmounted, reactive, defineProps } from 'vue'
import HighlightText from './highLightText.vue' import HighlightText from './highLightText.vue'
import { beautifyTime } from '@/utils/datetime' import { beautifyTime } from '@/utils/datetime'
import { ChatMsgTypeMapping, MessageComponents } from '@/constant/message' import { ChatMsgTypeMapping } from '@/constant/message'
const props = defineProps({ const props = defineProps({
searchItem: Object | Number, searchItem: Object | Number,
searchResultKey: { searchResultKey: {
@ -291,8 +255,6 @@ const resultDetail = computed(() => {
result_detail = result_detail =
props.searchItem?.msg_type === 1 props.searchItem?.msg_type === 1
? props.searchItem?.extra?.content ? props.searchItem?.extra?.content
: props.searchItem?.msg_type === 3 || props.searchItem?.msg_type === 6
? props.searchItem?.extra
: ChatMsgTypeMapping[props.searchItem?.msg_type] : ChatMsgTypeMapping[props.searchItem?.msg_type]
break break
default: default:
@ -300,22 +262,6 @@ const resultDetail = computed(() => {
} }
return result_detail return result_detail
}) })
const previewPDF = (item) => {
console.log(item)
// if (typeof plus !== 'undefined') {
// downloadAndOpenFile(item)
// } else {
// document.addEventListener('plusready', () => {
// downloadAndOpenFile(item)
// })
// }
window.open(
`${import.meta.env.VITE_PAGE_URL}/office?url=${item}`,
'_blank',
'width=1200,height=900,left=200,top=200,toolbar=no,menubar=no,scrollbars=yes,resizable=yes,location=no,status=no'
)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.search-item { .search-item {
@ -375,69 +321,6 @@ const previewPDF = (item) => {
color: #999999; color: #999999;
line-height: 20px; line-height: 20px;
} }
.file-message-wrapper {
.condition-each-result-attachments {
width: 289px;
height: 62px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 12px 15px;
background-color: #f3f3f3;
border-radius: 4px;
border-bottom: 1px solid #f8f8f8;
box-sizing: border-box;
.attachment-avatar {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-shrink: 0;
img {
width: 38px;
height: 38px;
}
}
.attachment-info {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
margin: 0 0 0 11px;
width: calc(100% - 38px - 11px);
.attachment-info-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 100%;
span {
line-height: 20px;
color: #191919;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.attachment-sub-info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 100%;
span {
line-height: 17px;
color: #999999;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
} }
.info-detail-searchRecordDetail { .info-detail-searchRecordDetail {
display: flex; display: flex;
@ -497,31 +380,4 @@ const previewPDF = (item) => {
} }
} }
} }
.message-component-wrapper {
width: 154px;
height: 100px;
display: inline-block;
overflow: hidden;
position: relative;
.im-message-video,
.im-message-image,
.image-container {
width: 100% !important;
height: 100% !important;
}
:deep(.n-image) {
width: 100% !important;
height: 100% !important;
}
:deep(img),
:deep(video) {
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
}
}
</style> </style>

View File

@ -16,16 +16,12 @@ const props = defineProps({
createdAt: { createdAt: {
type: String, type: String,
required: false required: false
}, }
modalTitle: {
type: String,
required: true
},
}) })
const isShow=defineModel<boolean>('show') const isShow=defineModel<boolean>('show')
const { showUserInfoModal } = useInject() const { showUserInfoModal } = useInject()
const items = ref<ITalkRecord[]>([]) const items = ref<ITalkRecord[]>([])
const title = ref(props?.modalTitle || '会话记录') const title = ref('会话记录')
const onMaskClick = () => { const onMaskClick = () => {
isShow.value=false isShow.value=false
@ -34,7 +30,7 @@ const onMaskClick = () => {
const onLoadData = () => { const onLoadData = () => {
ServeGetForwardRecords({ ServeGetForwardRecords({
msg_id: props.msgId, msg_id: props.msgId,
biz_date: parseTime(new Date(props.createdAt || ''), '{y}{m}') biz_date: parseTime(new Date(props.createdAt), '{y}{m}')
}).then((res) => { }).then((res) => {
if (res.code == 200) { if (res.code == 200) {
items.value = res.data.items || [] items.value = res.data.items || []

View File

@ -12,13 +12,7 @@ const props = defineProps<{
const isShowRecord = ref(false) const isShowRecord = ref(false)
const title = computed(() => { const title = computed(() => {
const uniqueNames = [...new Set(props.extra.records.map(v => v.nickname))]; return [...new Set(props.extra.records.map((v) => v.nickname))].join('、')
if (uniqueNames.length <= 2) {
return uniqueNames.join('和');
} else {
return uniqueNames.slice(0, 2).join('和') + '等';
}
// return [...new Set(props.extra.records.map((v) => v.nickname))].join('')
}) })
const onClick = () => { const onClick = () => {
@ -27,7 +21,7 @@ const onClick = () => {
</script> </script>
<template> <template>
<section class="im-message-forward pointer" @click="onClick"> <section class="im-message-forward pointer" @click="onClick">
<div class="title">{{ extra.forward_name || title}}的会话记录</div> <div class="title">{{ title }} 的会话记录</div>
<div class="list" v-for="(record, index) in extra.records" :key="index"> <div class="list" v-for="(record, index) in extra.records" :key="index">
<p> <p>
<span>{{ record.nickname }}: </span> <span>{{ record.nickname }}: </span>
@ -39,7 +33,7 @@ const onClick = () => {
<span>转发聊天会话记录 ({{ extra.msg_ids.length }})</span> <span>转发聊天会话记录 ({{ extra.msg_ids.length }})</span>
</div> </div>
<ForwardRecord v-model:show="isShowRecord" :msg-id="data.msg_id" @close="isShowRecord = false" :created-at="data.created_at" :modalTitle="(extra.forward_name || title) + '的会话记录'"/> <ForwardRecord v-model:show="isShowRecord" :msg-id="data.msg_id" @close="isShowRecord = false" :created-at="data.created_at"/>
</section> </section>
</template> </template>
@ -47,21 +41,19 @@ const onClick = () => {
.im-message-forward { .im-message-forward {
width: 250px; width: 250px;
min-height: 95px; min-height: 95px;
max-height: 190px; max-height: 150px;
border-radius: 10px; border-radius: 10px;
padding: 8px 10px; padding: 8px 10px;
border: 1px solid var(--im-message-border-color); border: 1px solid var(--im-message-border-color);
user-select: none; user-select: none;
.title { .title {
max-height: 60px; height: 30px;
line-height: 30px; line-height: 30px;
font-size: 15px; font-size: 15px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; white-space: nowrap;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-weight: 400; font-weight: 400;
margin-bottom: 5px; margin-bottom: 5px;
} }

View File

@ -11,20 +11,12 @@ defineProps<{
let show = ref(false) let show = ref(false)
</script> </script>
<template> <template>
<section <section class="im-message-group-notice pointer" @click="show = !show">
class="im-message-group-notice pointer"
@click="show = !show"
:class="{
left: data.float === 'left',
right: data.float === 'right'
}"
>
<div class="title"> <div class="title">
<!-- <n-tag :bordered="false" size="small" type="primary"> 群公告 </n-tag> <n-tag :bordered="false" size="small" type="primary"> 群公告 </n-tag>
{{ extra.title }} --> {{ extra.title }}
<text>群公告</text>
</div> </div>
<div class="title" :class="{ ellipsis: !show }"> <div class="content" :class="{ ellipsis: !show }">
{{ extra.content }} {{ extra.content }}
</div> </div>
</section> </section>
@ -38,14 +30,14 @@ let show = ref(false)
padding: 8px 10px; padding: 8px 10px;
border: 1px solid var(--im-message-border-color); border: 1px solid var(--im-message-border-color);
user-select: none; user-select: none;
background-color: #fff;
.title { .title {
line-height: 44rpx; height: 30px;
font-size: 32rpx; line-height: 30px;
// overflow: hidden; font-size: 14px;
// text-overflow: ellipsis; overflow: hidden;
// white-space: nowrap; text-overflow: ellipsis;
white-space: nowrap;
font-weight: 400; font-weight: 400;
margin-bottom: 5px; margin-bottom: 5px;
position: relative; position: relative;
@ -64,18 +56,5 @@ let show = ref(false)
white-space: nowrap; white-space: nowrap;
} }
} }
&.left {
background-color: #fff;
border-radius: 0 16rpx 16rpx 16rpx;
}
&.right {
background-color: #46299d;
border-radius: 16rpx 0 16rpx 16rpx;
.title {
color: #fff;
}
}
} }
</style> </style>

View File

@ -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 {

View File

@ -27,16 +27,6 @@ const props = defineProps({
data: { data: {
type: Object, type: Object,
default: () => {} default: () => {}
},
revokeInfo: {
type: Object,
default() {
return {}
}
},
extra: {
type: String,
default: ''
} }
}) })
@ -52,104 +42,16 @@ const onRevoke = () => {
</script> </script>
<template> <template>
<div class="im-message-revoke"> <div class="im-message-revoke">
<div class="content" v-if="JSON.stringify(revokeInfo) !== '{}'"> <div class="content">
<span v-if="talk_type === 1 && login_uid === revokeInfo.withdraw_id"> <div v-if="login_uid === user_id">
你撤回了一条消息 | {{ formatTime(datetime) }} <span> 你撤回了一条消息 | {{ formatTime(datetime) }} </span>
</span> <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>
<span v-if="talk_type === 1 && login_uid !== revokeInfo.withdraw_id">
{{ revokeInfo.withdraw_name }}撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span
v-if="
talk_type === 2 &&
login_uid === revokeInfo.withdraw_id &&
login_uid === revokeInfo.retracted_id
"
>
你撤回了一条消息 |
{{ formatTime(datetime) }}
<slot></slot>
</span>
<span
v-if="
talk_type === 2 &&
login_uid === revokeInfo.withdraw_id &&
login_uid !== revokeInfo.retracted_id
"
>
你撤回了{{ revokeInfo.retracted_name }}一条消息 |
{{ formatTime(datetime) }}
</span>
<span
v-if="
talk_type === 2 &&
login_uid !== revokeInfo.withdraw_id &&
revokeInfo.withdraw_id === revokeInfo.retracted_id
"
>
{{ revokeInfo.withdraw_name }}撤回了一条消息 |
{{ formatTime(datetime) }}
</span>
<span
v-if="
talk_type === 2 &&
login_uid !== revokeInfo.withdraw_id &&
login_uid === revokeInfo.retracted_id &&
revokeInfo.withdraw_id !== revokeInfo.retracted_id
"
>
{{ revokeInfo.withdraw_name }}撤回了你一条消息 |
{{ formatTime(datetime) }}
</span>
<span
v-if="
talk_type === 2 &&
login_uid !== revokeInfo.withdraw_id &&
login_uid !== revokeInfo.retracted_id &&
revokeInfo.withdraw_id !== revokeInfo.retracted_id
"
>
{{ revokeInfo.withdraw_name }}撤回了{{ revokeInfo.retracted_name }}一条消息 |
{{ formatTime(datetime) }}
</span>
<div style="display: inline-block;" v-if="login_uid === user_id">
<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-else-if="talk_type == 1"> 对方撤回了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else-if="login_uid == user_idB"> A撤回你了一条消息 | {{ formatTime(datetime) }} </span> <span v-else>
<span v-else> A撤回B了一条消息 | {{ formatTime(datetime) }} </span> --> "{{ nickname }}" 撤回了一条消息 |
</div> {{ formatTime(datetime) }}
<div class="content" v-if="JSON.stringify(revokeInfo) === '{}'">
<span v-if="talk_type === 1 && login_uid === user_id">
你撤回了一条消息 | {{ formatTime(datetime) }}
</span> </span>
<span v-if="talk_type === 1 && login_uid !== user_id">
{{ nickname }}撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && !extra && login_uid === user_id">
你撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && !extra && login_uid !== user_id">
{{ nickname }}撤回了一条消息 | {{ 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">
<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>
</template> </template>

View File

@ -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;
} }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
} }
} }
} }

View File

@ -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)
} }

View File

@ -96,6 +96,7 @@ export const MessageComponents = {
// 可转发的消息类型 // 可转发的消息类型
export const ForwardableMessageType = [ export const ForwardableMessageType = [
ChatMsgTypeForward,
ChatMsgTypeText, ChatMsgTypeText,
ChatMsgTypeCode, ChatMsgTypeCode,
ChatMsgTypeImage, ChatMsgTypeImage,

View File

@ -70,12 +70,6 @@ class Revoke extends Base {
useTalkStore().updateItem({ useTalkStore().updateItem({
index_name: this.getIndexName(), index_name: this.getIndexName(),
msg_text: this.resource.text, msg_text: this.resource.text,
revokeInfo: {
retracted_id: this.resource.retracted_id,
retracted_name: this.resource.retracted_name,
withdraw_id: this.resource.withdraw_id,
withdraw_name: this.resource.withdraw_name,
},
updated_at: parseTime(new Date()) updated_at: parseTime(new Date())
}) })
@ -86,12 +80,6 @@ class Revoke extends Base {
useDialogueStore().updateDialogueRecord({ useDialogueStore().updateDialogueRecord({
msg_id: this.msg_id, msg_id: this.msg_id,
revokeInfo: {
retracted_id: this.resource.retracted_id,
retracted_name: this.resource.retracted_name,
withdraw_id: this.resource.withdraw_id,
withdraw_name: this.resource.withdraw_name,
},
is_revoke: 1 is_revoke: 1
}) })
} }

View File

@ -245,8 +245,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)
} }
}) })
}, },

View File

@ -170,14 +170,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++
@ -209,10 +209,12 @@ export const useUploadsStore = defineStore('uploads', {
} }
} catch (error) { } catch (error) {
updatedItem.onProgress(-1)
console.error("分片上传错误:", error); console.error("分片上传错误:", error);
// 获取最新的项目状态 // 获取最新的项目状态
const updatedItem = this.findItem(uploadId) // 这里不应该重新定义变量而是使用已有的updatedItem
// const updatedItem = this.findItem(uploadId)
if (!updatedItem) return if (!updatedItem) return
// 如果是暂停导致的错误,不改变状态 // 如果是暂停导致的错误,不改变状态

View File

@ -51,8 +51,7 @@ export interface ITalkRecord {
float: string, float: string,
is_convert_text?:number//语音记录的 是否是在展示转文本状态 1是 0否, is_convert_text?:number//语音记录的 是否是在展示转文本状态 1是 0否,
erp_user_id:number, erp_user_id:number,
read_total_num:number, read_total_num:number
revokeInfo?: any
} }
export interface ITalkRecordExtraText { export interface ITalkRecordExtraText {
@ -82,7 +81,6 @@ export interface ITalkRecordExtraForward {
}[] }[]
talk_type: number talk_type: number
user_id: number user_id: number
forward_name?: any
} }
export interface ITalkRecordExtraGroupNotice { export interface ITalkRecordExtraGroupNotice {

View File

@ -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'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b8993ebccf0349a53e3197efc45b9dbe3f2bf1dc0dddce6787811964e76efefec3b3fd39fce15d43989c156413f12de3f0c74c1ff1d3c5da214d3bcefef7546498e37fa73453c749a56ea66777488bd3550'
} }
/** /**

View File

@ -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()

View File

@ -53,7 +53,13 @@ request.interceptors.request.use((config) => {
}, errorHandler) }, errorHandler)
// 响应拦截器 // 响应拦截器
request.interceptors.response.use((response) => response.data, errorHandler) request.interceptors.response.use((response) => {
console.log('response.data.status',response.data.status)
if(response.data.code !==200&&response.data.status!==0){
window['$message'].warning(response.data.msg)
}
return response.data
}, errorHandler)
/** /**
* GET 请求 * GET 请求

View File

@ -16,8 +16,6 @@ import avatarModule from '@/components/avatar-module/index.vue'
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) {

View File

@ -37,7 +37,6 @@ const labelColor=[
<div class="header"> <div class="header">
<div class="title"> <div class="title">
<span class="nickname">{{ username }}</span> <span class="nickname">{{ username }}</span>
<span v-if="data.talk_type == 2">({{data.group_member_num}})</span>
<!-- <span class="badge top" v-show="data.is_top"></span> <!-- <span class="badge top" v-show="data.is_top"></span>
<span class="badge roboot" v-show="data.is_robot"></span> <span class="badge roboot" v-show="data.is_robot"></span>
<span class="badge group" v-show="data.talk_type == 2"></span> --> <span class="badge group" v-show="data.talk_type == 2"></span> -->

View File

@ -59,7 +59,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,

View File

@ -182,7 +182,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('复制成功')
@ -330,6 +330,7 @@ const onContextMenuHandle = (key: string) => {
const onRowClick = (item: ITalkRecord) => { const onRowClick = (item: ITalkRecord) => {
if (dialogueStore.isOpenMultiSelect) { if (dialogueStore.isOpenMultiSelect) {
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 {
@ -766,8 +767,6 @@ const onCustomSkipBottomEvent = () => {
:nickname="item.nickname" :nickname="item.nickname"
:talk_type="item.talk_type" :talk_type="item.talk_type"
:datetime="item.created_at" :datetime="item.created_at"
:revokeInfo="item.revokeInfo"
:extra="item.extra"
/> />
</div> </div>

View File

@ -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)}`
@ -123,7 +126,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,

View File

@ -147,7 +147,6 @@ const onSetMenu = () => {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
margin: 0 5px 0 0;
} }
} }

View File

@ -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() {
@ -33,6 +52,7 @@ export function useMenu() {
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){
@ -48,9 +68,10 @@ export function useMenu() {
dropdown.options.push({ label: '多选', key: 'multiSelect' }) dropdown.options.push({ label: '多选', key: 'multiSelect' })
dropdown.options.push({ label: '引用', key: 'quote' }) dropdown.options.push({ label: '引用', key: 'quote' })
if (isRevoke(uid, item)|| (dialogueStore.groupInfo as any).is_manager) { if (canAddRevokeOption(uid, item, (dialogueStore.groupInfo as any).is_manager)) {
dropdown.options.push({ label: `撤回`, key: 'revoke' }) dropdown.options.push({ label: '撤回', key: 'revoke' });
} }
dropdown.options.push({ label: '删除', key: 'delete' }) dropdown.options.push({ label: '删除', key: 'delete' })

View File

@ -46,9 +46,9 @@ export default defineConfig(({ mode }) => {
vueJsx({}), vueJsx({}),
compressPlugin(), compressPlugin(),
UnoCSS(), UnoCSS(),
vueDevTools({ // vueDevTools({
launchEditor: 'trae', // launchEditor: 'trae',
}) // })
], ],
define: { define: {
__APP_ENV__: env.APP_ENV __APP_ENV__: env.APP_ENV