新增通讯录页面;新增发起单聊功能;新增聊天会话左滑删除并同时删除localStorage中的记录

This commit is contained in:
wangyifeng 2025-02-05 16:22:32 +08:00
parent 2e3b0b994b
commit 78ca543946
17 changed files with 607 additions and 348 deletions

View File

@ -135,6 +135,15 @@ export const ServeRemoveRecords = (data) => {
}) })
} }
//清空聊天记录
export const ServeEmptyMessage = (data) => {
return request({
url: '/api/v1/talk/message/empty',
method: 'POST',
data,
})
}
// 收藏表情包服务接口 // 收藏表情包服务接口
export const ServeCollectEmoticon = (data) => { export const ServeCollectEmoticon = (data) => {
return request({ return request({

View File

@ -12,7 +12,7 @@ import { reactive, nextTick, computed, h, inject } from 'vue'
// IdCard // IdCard
// } from '@icon-park/vue-next' // } from '@icon-park/vue-next'
import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat' import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat'
import { useDialogueStore, useTalkStore } from '@/store' import { useDialogueStore, useTalkStore, useDialogueListStore } from '@/store'
import { ServeSecedeGroup } from '@/api/group' import { ServeSecedeGroup } from '@/api/group'
// import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact' // import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact'
// import { NInput } from 'naive-ui' // import { NInput } from 'naive-ui'
@ -28,6 +28,7 @@ export function useSessionMenu() {
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
const talkStore = useTalkStore() const talkStore = useTalkStore()
const dialogueListStore = useDialogueListStore()
const user = inject('$user') const user = inject('$user')
@ -105,6 +106,7 @@ export function useSessionMenu() {
const onDeleteTalk = (index_name = '') => { const onDeleteTalk = (index_name = '') => {
talkStore.delItem(index_name) talkStore.delItem(index_name)
dialogueListStore.delDialogueStorage(index_name)
index_name === indexName.value && dialogueStore.$reset() index_name === indexName.value && dialogueStore.$reset()
} }
@ -264,5 +266,5 @@ export function useSessionMenu() {
evnets[key] && evnets[key](dropdown.item) evnets[key] && evnets[key](dropdown.item)
} }
return { dropdown, onCloseContextMenu, onContextMenuTalkHandle, onToTopTalk } return { dropdown, onCloseContextMenu, onContextMenuTalkHandle, onToTopTalk, onRemoveTalk }
} }

View File

@ -51,7 +51,7 @@
"type": "page", "type": "page",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"enablePullDownRefresh":false "enablePullDownRefresh": false
} }
}, },
{ {
@ -186,6 +186,14 @@
"navigationStyle": "custom", "navigationStyle": "custom",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
},
{
"path": "pages/addressBook/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
} }
], ],
"globalStyle": { "globalStyle": {

View File

@ -0,0 +1,30 @@
<template>
<div class="address-book-page">
<zPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar
:title="$t('index.mine.addressBook')"
:shadowNum="0"
></customNavbar>
</template>
<div class="address-book">
</div>
</zPaging>
</div>
</template>
<script setup>
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
</script>
<style scoped lang="scss">
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.address-book-page {
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: center bottom;
width: 100%;
}
</style>

View File

@ -19,12 +19,12 @@
" "
> >
<div class="group-member-avatar"> <div class="group-member-avatar">
<img v-if="memberItem.avatar" :src="memberItem.avatar" /> <img v-if="memberItem?.avatar" :src="memberItem?.avatar" />
<span v-if="!memberItem.avatar" class="text-[24rpx] font-bold"> <span v-if="!memberItem?.avatar" class="text-[24rpx] font-bold">
{{ {{
(memberItem.nickname || memberItem.nickName).length >= 2 (memberItem?.nickname || memberItem?.nickName)?.length >= 2
? (memberItem.nickname || memberItem.nickName).slice(-2) ? (memberItem?.nickname || memberItem?.nickName).slice(-2)
: memberItem.nickname || memberItem.nickName : memberItem?.nickname || memberItem?.nickName
}} }}
</span> </span>
</div> </div>
@ -90,7 +90,9 @@ const toUserDetailPage = (userItem) => {
uni.navigateTo({ uni.navigateTo({
url: url:
'/pages/dialog/dialogDetail/userDetail?erpUserId=' + '/pages/dialog/dialogDetail/userDetail?erpUserId=' +
(userItem.erp_user_id || userItem.ID), (userItem.erp_user_id || userItem.ID) +
'&user_id=' +
(userItem.user_id || userItem.id),
}) })
} }

View File

@ -7,7 +7,16 @@
</template> </template>
<div class="edit-group-info"> <div class="edit-group-info">
<div class="group-avatar"> <div class="group-avatar">
<img :src="state.groupAvatar" /> <avatarModule
:mode="2"
:avatar="groupParams?.groupInfo?.avatar"
:groupType="groupParams?.groupInfo?.group_type"
:customStyle="{
width: '460rpx',
height: '460rpx',
borderRadius: '0',
}"
></avatarModule>
</div> </div>
</div> </div>
<customBtn <customBtn
@ -19,6 +28,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import avatarModule from '@/components/avatar-module/index.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue' import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ServeEditGroup } from '@/api/group/index.js' import { ServeEditGroup } from '@/api/group/index.js'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue' import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
@ -39,18 +49,6 @@ const dialogueParams = reactive({
receiver_id: computed(() => dialogueStore.talk.receiver_id), receiver_id: computed(() => dialogueStore.talk.receiver_id),
}) })
const state = reactive({
groupAvatar: '', //
})
watch(
() => groupParams.groupInfo,
(newGroupInfo) => {
state.groupAvatar = newGroupInfo.avatar
},
{ deep: true },
)
const onProgressFn = (progress, id) => { const onProgressFn = (progress, id) => {
console.log((progress.loaded / progress.total) * 100, 'progress') console.log((progress.loaded / progress.total) * 100, 'progress')
@ -62,9 +60,6 @@ const onProgressFn = (progress, id) => {
onLoad((options) => { onLoad((options) => {
console.log(options) console.log(options)
if (options.groupAvatar) {
state.groupAvatar = options.groupAvatar
}
}) })
// //
@ -138,10 +133,6 @@ const editAvatar = () => {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
img {
width: 460rpx;
height: 460rpx;
}
} }
} }
</style> </style>

View File

@ -9,7 +9,15 @@
</template> </template>
<div class="edit-group-info"> <div class="edit-group-info">
<div class="group-avatar"> <div class="group-avatar">
<img :src="state.groupAvatar" /> <avatarModule
:mode="2"
:avatar="groupParams?.groupInfo?.avatar"
:groupType="groupParams?.groupInfo?.group_type"
:customStyle="{
width: '192rpx',
height: '192rpx',
}"
></avatarModule>
</div> </div>
<div class="group-name"> <div class="group-name">
<span class="text-[28rpx] font-medium"> <span class="text-[28rpx] font-medium">
@ -42,6 +50,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import avatarModule from '@/components/avatar-module/index.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue' import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ServeEditGroup } from '@/api/group/index.js' import { ServeEditGroup } from '@/api/group/index.js'
import { useGroupStore, useDialogueStore } from '@/store' import { useGroupStore, useDialogueStore } from '@/store'
@ -61,15 +70,11 @@ const dialogueParams = reactive({
const state = reactive({ const state = reactive({
pageTitle: '', // pageTitle: '', //
groupAvatar: '', //
groupName: '', // groupName: '', //
}) })
onLoad((options) => { onLoad((options) => {
console.log(options) console.log(options)
if (options.groupAvatar) {
state.groupAvatar = options.groupAvatar
}
state.groupName = groupParams.groupInfo.group_name state.groupName = groupParams.groupInfo.group_name
}) })
@ -126,11 +131,6 @@ const confirmEdit = () => {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
img {
width: 192rpx;
height: 192rpx;
border-radius: 50%;
}
} }
.group-name { .group-name {
padding: 0 32rpx; padding: 0 32rpx;

View File

@ -8,7 +8,12 @@
<div class="chat-settings"> <div class="chat-settings">
<div class="chat-group-base-infos chat-settings-card"> <div class="chat-group-base-infos chat-settings-card">
<div class="base-info-avatar" @click="toEditAvatarPage"> <div class="base-info-avatar" @click="toEditAvatarPage">
<img :src="groupAvatar" /> <avatarModule
:mode="2"
:avatar="groupParams?.groupInfo?.avatar"
:groupType="groupParams?.groupInfo?.group_type"
:customStyle="{ width: '96rpx', height: '96rpx' }"
></avatarModule>
</div> </div>
<div class="base-info"> <div class="base-info">
<div class="base-info-name"> <div class="base-info-name">
@ -161,11 +166,8 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import avatarModule from '@/components/avatar-module/index.vue'
import useConfirm from '@/components/x-confirm/useConfirm.js' import useConfirm from '@/components/x-confirm/useConfirm.js'
import groupDepartment from '@/static/image/chatList/groupDepartment.png'
import groupProject from '@/static/image/chatList/groupProject.png'
import groupNormal from '@/static/image/chatList/groupNormal.png'
import groupCompany from '@/static/image/chatList/groupCompany.png'
import recordSearchTypeIcon_groupMember from '@/static/image/chatSettings/recordSearchTypeGroupMembers.png' import recordSearchTypeIcon_groupMember from '@/static/image/chatSettings/recordSearchTypeGroupMembers.png'
import recordSearchTypeIcon_date from '@/static/image/chatSettings/recordSearchTypeDate.png' import recordSearchTypeIcon_date from '@/static/image/chatSettings/recordSearchTypeDate.png'
import recordSearchTypeIcon_imgAndVideo from '@/static/image/chatSettings/recordSearchTypeImgAndVideo.png' import recordSearchTypeIcon_imgAndVideo from '@/static/image/chatSettings/recordSearchTypeImgAndVideo.png'
@ -183,7 +185,7 @@ import {
useGroupTypeStore, useGroupTypeStore,
} from '@/store' } from '@/store'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { ServeInviteGroup } from '@/api/group/index' import { ServeInviteGroup, ServeDismissGroup } from '@/api/group/index'
import { ServeTopTalkList, ServeSetNotDisturb } from '@/api/chat/index' import { ServeTopTalkList, ServeSetNotDisturb } from '@/api/chat/index'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
@ -306,14 +308,6 @@ onMounted(() => {
] ]
}) })
//
const groupAvatar = computed(() => {
return (
groupParams?.groupInfo?.avatar ||
groupTypeMapping[groupParams?.groupInfo?.group_type]?.defaultImg
)
})
// //
const groupName = computed(() => { const groupName = computed(() => {
return groupParams?.groupInfo?.group_name return groupParams?.groupInfo?.group_name
@ -326,27 +320,21 @@ const groupNum = computed(() => {
// -groupType // -groupType
const groupTypeMapping = { const groupTypeMapping = {
0: { 0: {},
defaultImg: 'textImg',
},
1: { 1: {
result_type: t('index.mine.normal'), result_type: t('index.mine.normal'),
defaultImg: groupNormal,
}, },
2: { 2: {
result_type: t('index.mine.department'), result_type: t('index.mine.department'),
result_type_color: '#377EC6', result_type_color: '#377EC6',
defaultImg: groupDepartment,
}, },
3: { 3: {
result_type: t('index.mine.project'), result_type: t('index.mine.project'),
result_type_color: '#C1681C', result_type_color: '#C1681C',
defaultImg: groupProject,
}, },
4: { 4: {
result_type: t('index.type.company'), result_type: t('index.type.company'),
result_type_color: '#7A58DE', result_type_color: '#7A58DE',
defaultImg: groupCompany,
}, },
} }
@ -420,9 +408,7 @@ const updateGroupInfos = () => {
// //
const toEditGroupInfoPage = () => { const toEditGroupInfoPage = () => {
uni.navigateTo({ uni.navigateTo({
url: url: '/pages/chatSettings/groupManage/editGroupName',
'/pages/chatSettings/groupManage/editGroupName?groupAvatar=' +
groupAvatar.value,
}) })
} }
@ -432,9 +418,7 @@ const toEditAvatarPage = () => {
return return
} }
uni.navigateTo({ uni.navigateTo({
url: url: '/pages/chatSettings/groupManage/editAvatar',
'/pages/chatSettings/groupManage/editAvatar?groupAvatar=' +
groupAvatar.value,
}) })
} }
@ -447,9 +431,7 @@ const toManagePage = (label) => {
return return
} }
uni.navigateTo({ uni.navigateTo({
url: url: '/pages/chatSettings/groupManage/editGroupName',
'/pages/chatSettings/groupManage/editGroupName?groupAvatar=' +
groupAvatar.value,
}) })
} else if (label === t('chat.settings.groupNotice')) { } else if (label === t('chat.settings.groupNotice')) {
uni.navigateTo({ uni.navigateTo({
@ -547,12 +529,15 @@ const showConfirmPrompt = (flag) => {
let subContent = '' let subContent = ''
let subContentColor = '' let subContentColor = ''
if (flag === 1) { if (flag === 1) {
//
confirmContent = t('ok') + t('chat.settings.clearChatRecord') confirmContent = t('ok') + t('chat.settings.clearChatRecord')
} else if (flag === 2) { } else if (flag === 2) {
//
confirmContent = t('ok') + t('group.quit.btn') confirmContent = t('ok') + t('group.quit.btn')
subContent = t('groupManage.disband.hint') subContent = t('groupManage.disband.hint')
subContentColor = '#CF3050' subContentColor = '#CF3050'
} else if (flag === 3) { } else if (flag === 3) {
//退
confirmContent = t('ok') + t('group.quit.btn') confirmContent = t('ok') + t('group.quit.btn')
subContent = t('groupManage.quit.hint') subContent = t('groupManage.quit.hint')
subContentColor = '#B4B4B4' subContentColor = '#B4B4B4'
@ -565,7 +550,24 @@ const showConfirmPrompt = (flag) => {
contentText: t('ok'), contentText: t('ok'),
confirmText: t('ok'), confirmText: t('ok'),
cancelText: t('cancel'), cancelText: t('cancel'),
onConfirm: () => {}, onConfirm: async () => {
if (flag === 1) {
useDialogueStore().apiClearRecord()
} else if (flag === 2) {
let params = {
group_id: dialogueParams.receiver_id, //id
}
console.log(params)
const res = await ServeDismissGroup(params)
if (res.code === 200) {
useDialogueStore().updateGroupMembers()
groupStore.ServeGroupDetail()
uni.navigateBack({
delta: 2,
})
}
}
},
onCancel: () => {}, onCancel: () => {},
}) })
} }
@ -630,16 +632,7 @@ const inviteMembersInGroup = async (memberList) => {
padding: 32rpx 40rpx 32rpx 20rpx; padding: 32rpx 40rpx 32rpx 20rpx;
.base-info-avatar { .base-info-avatar {
width: 96rpx;
height: 96rpx;
flex-shrink: 0; flex-shrink: 0;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
} }
.base-info { .base-info {
width: 100%; width: 100%;

View File

@ -146,7 +146,7 @@
<div class="userAvatar flex items-center justify-center"> <div class="userAvatar flex items-center justify-center">
{{ item.nickName.slice(-2) }} {{ item.nickName.slice(-2) }}
</div> </div>
<div class="ml-[20rpx] flex flex-col items-center"> <div class="ml-[20rpx] flex flex-col justify-center">
<div class="text-[28rpx] font-bold">{{ item.nickName }}</div> <div class="text-[28rpx] font-bold">{{ item.nickName }}</div>
<div class="text-[20rpx] text-[#747474]"> <div class="text-[20rpx] text-[#747474]">
{{ item.jobNum }} {{ item.jobNum }}
@ -154,11 +154,11 @@
</div> </div>
<tm-popover position="tc"> <tm-popover position="tc">
<div <div
class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]" class="ml-[6rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]"
> >
<div <div
v-for="post in item.positions" v-for="post in item.positions"
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]" class="postTag truncate mb-[4rpx] ml-[14rpx] max-w-[164rpx]"
> >
{{ post.name }} {{ post.name }}
</div> </div>

View File

@ -259,7 +259,7 @@ const handleConfirm = async () => {
if (allChooseMembers?.value?.length > 0) { if (allChooseMembers?.value?.length > 0) {
allChooseMembers?.value?.forEach((ele) => { allChooseMembers?.value?.forEach((ele) => {
if (!erp_ids) { if (!erp_ids) {
erp_ids = ele.ID erp_ids = String(ele.ID)
} else { } else {
erp_ids += ',' + ele.ID erp_ids += ',' + ele.ID
} }

View File

@ -60,6 +60,7 @@
:isBottom="true" :isBottom="true"
:btnText="$t('user.detail.sendMsg')" :btnText="$t('user.detail.sendMsg')"
:subBtnText="$t('user.detail.ringBell')" :subBtnText="$t('user.detail.ringBell')"
@clickBtn="toTalkUser"
></customBtn> ></customBtn>
</template> </template>
</ZPaging> </ZPaging>
@ -72,6 +73,9 @@ import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { reactive } from 'vue' import { reactive } from 'vue'
import { useTalkStore } from '@/store'
const talkStore = useTalkStore()
import { getUserInfoByClickAvatar } from '@/api/user/index' import { getUserInfoByClickAvatar } from '@/api/user/index'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@ -79,6 +83,7 @@ const { t } = useI18n()
const state = reactive({ const state = reactive({
erpUserId: '', //erpid erpUserId: '', //erpid
userId: '', //Id
userInfo: null, // userInfo: null, //
userBasicInfos: [], // userBasicInfos: [], //
}) })
@ -86,15 +91,18 @@ const state = reactive({
onLoad((options) => { onLoad((options) => {
console.log(options) console.log(options)
if (options.erpUserId) { if (options.erpUserId) {
state.erpUserId = options.erpUserId state.erpUserId = Number(options.erpUserId)
getUserInfo() getUserInfo()
} }
if(options.user_id){
state.userId = Number(options.user_id)
}
}) })
// //
const getUserInfo = () => { const getUserInfo = () => {
let params = { let params = {
erp_user_id: Number(state.erpUserId), erp_user_id: state.erpUserId,
} }
console.log(params) console.log(params)
const resp = getUserInfoByClickAvatar(params) const resp = getUserInfoByClickAvatar(params)
@ -166,6 +174,11 @@ const getUserInfo = () => {
resp.catch(() => {}) resp.catch(() => {})
} }
//
const toTalkUser = () => {
talkStore.toTalk(1, state.userId, state.erpUserId)
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.outer-layer { .outer-layer {

View File

@ -8,18 +8,36 @@
</div> </div>
<template v-slot:right> <template v-slot:right>
<div class="mr-[36rpx]"> <div class="mr-[36rpx]">
<tm-icon color="rgb(51, 51, 51)" :font-size="36" name="tmicon-gengduo" @click="toChatSettingsPage"></tm-icon> <tm-icon
color="rgb(51, 51, 51)"
:font-size="36"
name="tmicon-gengduo"
@click="toChatSettingsPage"
></tm-icon>
</div> </div>
</template> </template>
</tm-navbar> </tm-navbar>
</div> </div>
<div class="root"> <div class="root">
<div class="dialogBox"> <div class="dialogBox">
<ZPaging :fixed="false" use-chat-record-mode :use-page-scroll="false" :refresher-enabled="false" <ZPaging
:show-scrollbar="false" :loading-more-enabled="false" :hide-empty-view="true" height="100%" ref="zpagingRef" :fixed="false"
:use-virtual-list="true" :preload-page="1" cell-height-mode="dynamic" virtual-scroll-fps="80" use-chat-record-mode
:loading-more-custom-style="{ display: 'none', height: '0' }" @virtualListChange="virtualListChange" :use-page-scroll="false"
@scrolltolower="onRefreshLoad"> :refresher-enabled="false"
:show-scrollbar="false"
:loading-more-enabled="false"
:hide-empty-view="true"
height="100%"
ref="zpagingRef"
:use-virtual-list="true"
:preload-page="1"
cell-height-mode="dynamic"
virtual-scroll-fps="80"
:loading-more-custom-style="{ display: 'none', height: '0' }"
@virtualListChange="virtualListChange"
@scrolltolower="onRefreshLoad"
>
<!-- <template #top> <!-- <template #top>
<div class="load-toolbar pointer"> <div class="load-toolbar pointer">
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span> <span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
@ -29,55 +47,103 @@
</template> --> </template> -->
<!-- 数据加载状态栏 --> <!-- 数据加载状态栏 -->
<div class="message-item" v-for="item in virtualList" :id="`zp-id-${item.zp_index}`" :key="item.zp_index" <div
style="transform: scaleY(-1);"> class="message-item"
v-for="item in virtualList"
:id="`zp-id-${item.zp_index}`"
:key="item.zp_index"
style="transform: scaleY(-1);"
>
<!-- 系统消息 --> <!-- 系统消息 -->
<div v-if="item.msg_type >= 1000" class="message-box"> <div v-if="item.msg_type >= 1000" class="message-box">
<component :is="MessageComponents[item.msg_type] || 'unknown-message'" :extra="item.extra" :data="item" /> <component
:is="MessageComponents[item.msg_type] || 'unknown-message'"
:extra="item.extra"
:data="item"
/>
</div> </div>
<!-- 撤回消息 --> <!-- 撤回消息 -->
<div v-else-if="item.is_revoke == 1" class="message-box"> <div v-else-if="item.is_revoke == 1" class="message-box">
<revoke-message :login_uid="userStore.uid" :user_id="item.user_id" :nickname="item.nickname" <revoke-message
:talk_type="item.talk_type" :datetime="item.created_at" /> :login_uid="userStore.uid"
:user_id="item.user_id"
:nickname="item.nickname"
:talk_type="item.talk_type"
:datetime="item.created_at"
/>
</div> </div>
<div v-else class="message-box record-box" :class="{ <div
'direction-rt': item.float == 'right', v-else
'multi-select': dialogueStore.isOpenMultiSelect, class="message-box record-box"
'multi-select-check': item.isCheck :class="{
}"> 'direction-rt': item.float == 'right',
'multi-select': dialogueStore.isOpenMultiSelect,
'multi-select-check': item.isCheck,
}"
>
<!-- 多选按钮 --> <!-- 多选按钮 -->
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column"> <aside
v-if="dialogueStore.isOpenMultiSelect"
class="checkbox-column"
>
<!-- <n-checkbox size="small" :checked="item.isCheck" @update:checked="item.isCheck = !item.isCheck" /> --> <!-- <n-checkbox size="small" :checked="item.isCheck" @update:checked="item.isCheck = !item.isCheck" /> -->
<tm-checkbox :round="10" :defaultChecked="item.isCheck" <tm-checkbox
@update:modelValue="item.isCheck = !item.isCheck" :size="42" color="#46299D"></tm-checkbox> :round="10"
:defaultChecked="item.isCheck"
@update:modelValue="item.isCheck = !item.isCheck"
:size="42"
color="#46299D"
></tm-checkbox>
</aside> </aside>
<!-- 头像信息 --> <!-- 头像信息 -->
<aside class="avatar-column" @click="toUserDetailPage(item)"> <aside class="avatar-column" @click="toUserDetailPage(item)">
<im-avatar class="pointer" :src="item.avatar" :size="80" :username="item.nickname" <im-avatar
@click="showUserInfoModal(item.user_id)" /> class="pointer"
:src="item.avatar"
:size="80"
:username="item.nickname"
@click="showUserInfoModal(item.user_id)"
/>
</aside> </aside>
<!-- 主体信息 --> <!-- 主体信息 -->
<main class="main-column"> <main class="main-column">
<div class="talk-title"> <div class="talk-title">
<span class="nickname pointer" v-show="talkParams.type == 2 && item.float == 'left'" <span
@click="onClickNickname(item)"> class="nickname pointer"
<span class="at">@</span>{{ item.nickname }} v-show="talkParams.type == 2 && item.float == 'left'"
@click="onClickNickname(item)"
>
<span class="at">@</span>
{{ item.nickname }}
</span>
<span>
{{ parseTime(item.created_at, '{m}/{d} {h}:{i}') }}
</span> </span>
<span>{{ parseTime(item.created_at, '{m}/{d} {h}:{i}') }}</span>
</div> </div>
<div class="talk-content" :class="{ pointer: dialogueStore.isOpenMultiSelect }"> <div
class="talk-content"
:class="{ pointer: dialogueStore.isOpenMultiSelect }"
>
<deepBubble <deepBubble
@clickMenu="(menuType) => onContextMenu(menuType, item)" @clickMenu="(menuType) => onContextMenu(menuType, item)"
:isShowCopy="isShowCopy(item)" :isShowCopy="isShowCopy(item)"
:isShowWithdraw="isRevoke(talkParams.uid,item)" :isShowWithdraw="isRevoke(talkParams.uid, item)"
> >
<component class="component-content" :key="item.zp_index" <component
:is="MessageComponents[item.msg_type] || 'unknown-message'" :extra="item.extra" :data="item" class="component-content"
:max-width="true" :source="'panel'" /> :key="item.zp_index"
:is="
MessageComponents[item.msg_type] || 'unknown-message'
"
:extra="item.extra"
:data="item"
:max-width="true"
:source="'panel'"
/>
</deepBubble> </deepBubble>
<!-- <div class="talk-tools"> <!-- <div class="talk-tools">
<template v-if="talkParams.type == 1 && item.float == 'right'"> <template v-if="talkParams.type == 1 && item.float == 'right'">
@ -91,8 +157,11 @@
</div> --> </div> -->
</div> </div>
<div v-if="item.extra.reply" class="talk-reply pointer" <div
@click="onJumpMessage(item.extra?.reply?.msg_id)"> v-if="item.extra.reply"
class="talk-reply pointer"
@click="onJumpMessage(item.extra?.reply?.msg_id)"
>
<!-- <n-icon :component="ToTop" size="14" class="icon-top" /> --> <!-- <n-icon :component="ToTop" size="14" class="icon-top" /> -->
<span class="ellipsis"> <span class="ellipsis">
回复 {{ item.extra?.reply?.nickname }}: 回复 {{ item.extra?.reply?.nickname }}:
@ -101,60 +170,102 @@
</div> </div>
</main> </main>
</div> </div>
</div> </div>
<div class="load-toolbar pointer" style="transform: scaleY(-1);"> <div class="load-toolbar pointer" style="transform: scaleY(-1);">
<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 v-else class="no-more"> 没有更多消息了 </span> 查看更多消息 ...
</span>
<span v-else class="no-more">没有更多消息了</span>
</div> </div>
</ZPaging> </ZPaging>
</div> </div>
</div> </div>
<div class="footBox"> <div class="footBox">
<div v-if="!dialogueStore.isOpenMultiSelect"> <div v-if="!dialogueStore.isOpenMultiSelect">
<div class="mt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-center justify-between"> <div
class="mt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-center justify-between"
>
<div class="flex-1 quillBox"> <div class="flex-1 quillBox">
<QuillEditor ref="editor" id="editor" :options="editorOption" @editorChange="onEditorChange" <QuillEditor
style="height: 100%; border: none" /> ref="editor"
id="editor"
:options="editorOption"
@editorChange="onEditorChange"
style="height: 100%; border: none;"
/>
<!-- <tm-input type=textarea autoHeight focusColor="#F9F9F9" color="#F9F9F9" :inputPadding="[12]" <!-- <tm-input type=textarea autoHeight focusColor="#F9F9F9" color="#F9F9F9" :inputPadding="[12]"
placeholder=""></tm-input> --> placeholder=""></tm-input> -->
</div> </div>
<tm-image :margin="[10, 0]" @click="handleEmojiPanel" :width="52" :height="52" :round="12" <tm-image
:src="state.isOpenEmojiPanel ? keyboard : smile"></tm-image> :margin="[10, 0]"
<tm-image @click="handleFilePanel" :margin="[10, 0]" :width="52" :height="52" :round="12" @click="handleEmojiPanel"
:src="addCircleGray"></tm-image> :width="52"
<tm-button @click="onSendMessageClick" :margin="[0, 0]" :padding="[0, 30]" color="#46299D" :fontSize="28" :height="52"
size="mini" :shadow="0" label="发送"></tm-button> :round="12"
:src="state.isOpenEmojiPanel ? keyboard : smile"
></tm-image>
<tm-image
@click="handleFilePanel"
:margin="[10, 0]"
:width="52"
:height="52"
:round="12"
:src="addCircleGray"
></tm-image>
<tm-button
@click="onSendMessageClick"
:margin="[0, 0]"
:padding="[0, 30]"
color="#46299D"
:fontSize="28"
size="mini"
:shadow="0"
label="发送"
></tm-button>
</div> </div>
<div v-if="state.isOpenEmojiPanel" class="mt-[50rpx]"> <div v-if="state.isOpenEmojiPanel" class="mt-[50rpx]">
<emojiPanel @on-select="onEmoticonEvent" /> <emojiPanel @on-select="onEmoticonEvent" />
</div> </div>
<div v-if="state.isOpenFilePanel" class="mt-[16rpx]"> <div v-if="state.isOpenFilePanel" class="mt-[16rpx]">
<filePanel @selectImg="handleSelectImg" :talkParams="talkParams" /> <filePanel @selectImg="handleSelectImg" :talkParams="talkParams" />
</div> </div>
</div> </div>
<div v-else class="h-[232rpx]"> <div v-else class="h-[232rpx]">
<div class="flex items-center justify-center mt-[12rpx] text-[24rpx] text-[#747474] leading-[44rpx]"> <div
class="flex items-center justify-center mt-[12rpx] text-[24rpx] text-[#747474] leading-[44rpx]"
>
<div class="mr-[8rpx]">已选中:</div> <div class="mr-[8rpx]">已选中:</div>
<div>{{ selectedMessage.length }}条消息</div> <div>{{ selectedMessage.length }}条消息</div>
</div> </div>
<div class="flex items-center justify-around pl-[128rpx] pr-[128rpx] mt-[18rpx] text-[20rpx] text-[#737373] "> <div
<div @click="handleMergeForward" class="flex flex-col items-center justify-center"> class="flex items-center justify-around pl-[128rpx] pr-[128rpx] mt-[18rpx] text-[20rpx] text-[#737373]"
>
<div
@click="handleMergeForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6050"></tm-image> <tm-image :width="68" :height="68" :src="zu6050"></tm-image>
<div class="mt-[6rpx]">合并转发</div> <div class="mt-[6rpx]">合并转发</div>
</div> </div>
<div @click="handleSingleForward" class="flex flex-col items-center justify-center"> <div
@click="handleSingleForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6051"></tm-image> <tm-image :width="68" :height="68" :src="zu6051"></tm-image>
<div class="mt-[6rpx]">逐条转发</div> <div class="mt-[6rpx]">逐条转发</div>
</div> </div>
<div @click="handleWechatForward" class="flex flex-col items-center justify-center"> <div
@click="handleWechatForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6052"></tm-image> <tm-image :width="68" :height="68" :src="zu6052"></tm-image>
<div class="mt-[6rpx]">微信</div> <div class="mt-[6rpx]">微信</div>
</div> </div>
<div @click="handleDelete" class="flex flex-col items-center justify-center"> <div
@click="handleDelete"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6053"></tm-image> <tm-image :width="68" :height="68" :src="zu6053"></tm-image>
<div class="mt-[6rpx]">删除</div> <div class="mt-[6rpx]">删除</div>
</div> </div>
@ -170,16 +281,24 @@
:height="416" :height="416"
:round="6" :round="6"
> >
<div class="w-full h-full flex flex-col items-center" > <div class="w-full h-full flex flex-col items-center">
<div class="mt-[46rpx] mb-[44rpx] leading-[48rpx] text-[#747474] text-[24rpx]" > <div
class="mt-[46rpx] mb-[44rpx] leading-[48rpx] text-[#747474] text-[24rpx]"
>
撤回该条消息 撤回该条消息
</div> </div>
<div class="divider" ></div> <div class="divider"></div>
<div @click="withdrawerConfirm" class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#CF3050] leading-[48rpx]"> <div
@click="withdrawerConfirm"
class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#CF3050] leading-[48rpx]"
>
撤回 撤回
</div> </div>
<div class="divider" ></div> <div class="divider"></div>
<div @click="state.showWin = false" class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#000000] leading-[48rpx]"> <div
@click="state.showWin = false"
class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#000000] leading-[48rpx]"
>
取消 取消
</div> </div>
</div> </div>
@ -188,11 +307,19 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, watch, computed, onMounted, onUnmounted, nextTick } from 'vue'; import {
ref,
reactive,
watch,
computed,
onMounted,
onUnmounted,
nextTick,
} from 'vue'
import { QuillEditor, Quill } from '@vueup/vue-quill' import { QuillEditor, Quill } from '@vueup/vue-quill'
import EmojiBlot from './formats/emoji' import EmojiBlot from './formats/emoji'
import { useChatList } from "@/store/chatList/index.js"; import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from "@/store/auth"; import { useAuth } from '@/store/auth'
import { import {
useUserStore, useUserStore,
useDialogueStore, useDialogueStore,
@ -200,41 +327,45 @@ import {
useEditorDraftStore, useEditorDraftStore,
useTalkStore, useTalkStore,
useSettingsStore, useSettingsStore,
useDialogueListStore useDialogueListStore,
} from '@/store' } from '@/store'
import addCircleGray from "@/static/image/chatList/addCircleGray.png"; import addCircleGray from '@/static/image/chatList/addCircleGray.png'
import { MessageComponents, ForwardableMessageType } from '@/constant/message' import { MessageComponents, ForwardableMessageType } from '@/constant/message'
import { formatTime, parseTime } from '@/utils/datetime' import { formatTime, parseTime } from '@/utils/datetime'
import { deltaToMessage, deltaToString, isEmptyDelta } from './util' import { deltaToMessage, deltaToString, isEmptyDelta } from './util'
import smile from "@/static/image/chatList/smile@2x.png"; import smile from '@/static/image/chatList/smile@2x.png'
import keyboard from "@/static/image/chatList/keyboard@2x.png"; import keyboard from '@/static/image/chatList/keyboard@2x.png'
import { useInject, useTalkRecord } from '@/hooks' import { useInject, useTalkRecord } from '@/hooks'
import { emitCall } from '@/utils/common' import { emitCall } from '@/utils/common'
import ZPaging from "@/uni_modules/z-paging/components/z-paging/z-paging.vue"; import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from "@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js"; import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import emojiPanel from './components/emojiPanel.vue' import emojiPanel from './components/emojiPanel.vue'
import filePanel from './components/filePanel.vue' import filePanel from './components/filePanel.vue'
import lodash from "lodash"; import lodash from 'lodash'
import { ServePublishMessage } from '@/api/chat' import { ServePublishMessage } from '@/api/chat'
import copy07 from "@/static/image/chatList/copy07@2x.png" import copy07 from '@/static/image/chatList/copy07@2x.png'
import multipleChoices from "@/static/image/chatList/multipleChoices@2x.png" import multipleChoices from '@/static/image/chatList/multipleChoices@2x.png'
import cite from "@/static/image/chatList/cite@2x.png" import cite from '@/static/image/chatList/cite@2x.png'
import withdraw from "@/static/image/chatList/withdraw@2x.png" import withdraw from '@/static/image/chatList/withdraw@2x.png'
import delete07 from "@/static/image/chatList/delete@2x.png" import delete07 from '@/static/image/chatList/delete@2x.png'
import zu6050 from "@/static/image/chatList/zu6050@2x.png" import zu6050 from '@/static/image/chatList/zu6050@2x.png'
import zu6051 from "@/static/image/chatList/zu6051@2x.png" import zu6051 from '@/static/image/chatList/zu6051@2x.png'
import zu6052 from "@/static/image/chatList/zu6052@2x.png" import zu6052 from '@/static/image/chatList/zu6052@2x.png'
import zu6053 from "@/static/image/chatList/zu6053@2x.png" import zu6053 from '@/static/image/chatList/zu6053@2x.png'
import deepBubble from "@/components/deep-bubble/deep-bubble.vue" import deepBubble from '@/components/deep-bubble/deep-bubble.vue'
import {isRevoke } from './menu' import { isRevoke } from './menu'
import useConfirm from '@/components/x-confirm/useConfirm.js' import useConfirm from '@/components/x-confirm/useConfirm.js'
import { onLoad as uniOnload } from '@dcloudio/uni-app' import { onLoad as uniOnload } from '@dcloudio/uni-app'
Quill.register('formats/emoji', EmojiBlot) Quill.register('formats/emoji', EmojiBlot)
const { getDialogueList, updateZpagingRef, virtualList } = useDialogueListStore() const {
getDialogueList,
updateZpagingRef,
virtualList,
} = useDialogueListStore()
const talkStore = useTalkStore() const talkStore = useTalkStore()
const { showConfirm } = useConfirm(); const { showConfirm } = useConfirm()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const userStore = useUserStore() const userStore = useUserStore()
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
@ -251,7 +382,7 @@ const talkParams = reactive({
username: computed(() => dialogueStore.talk.username), username: computed(() => dialogueStore.talk.username),
online: computed(() => dialogueStore.online), online: computed(() => dialogueStore.online),
keyboard: computed(() => dialogueStore.keyboard), keyboard: computed(() => dialogueStore.keyboard),
num: computed(() => dialogueStore.members.length) num: computed(() => dialogueStore.members.length),
}) })
const state = ref({ const state = ref({
@ -259,7 +390,7 @@ const state = ref({
isOpenFilePanel: false, isOpenFilePanel: false,
showWin: false, showWin: false,
onfocusItem: null, onfocusItem: null,
sessionId: '' sessionId: '',
}) })
uniOnload((options) => { uniOnload((options) => {
@ -269,11 +400,11 @@ uniOnload((options) => {
}) })
const handleEmojiPanel = () => { const handleEmojiPanel = () => {
state.value.isOpenFilePanel = false; state.value.isOpenFilePanel = false
state.value.isOpenEmojiPanel = !state.value.isOpenEmojiPanel state.value.isOpenEmojiPanel = !state.value.isOpenEmojiPanel
} }
const handleFilePanel = () => { const handleFilePanel = () => {
state.value.isOpenEmojiPanel = false; state.value.isOpenEmojiPanel = false
state.value.isOpenFilePanel = !state.value.isOpenFilePanel state.value.isOpenFilePanel = !state.value.isOpenFilePanel
} }
@ -282,8 +413,8 @@ const onSendMessage = (data = {}) => {
...data, ...data,
receiver: { receiver: {
receiver_id: talkParams.receiver_id, receiver_id: talkParams.receiver_id,
talk_type: talkParams.type talk_type: talkParams.type,
} },
} }
ServePublishMessage(message) ServePublishMessage(message)
@ -313,10 +444,11 @@ const onSendMessageClick = () => {
return message.info('发送内容超长,请分条发送') return message.info('发送内容超长,请分条发送')
} }
onSendTextEvent({ onSendTextEvent({
data, callBack: (ok) => { data,
callBack: (ok) => {
if (!ok) return if (!ok) return
getQuill().setContents([], Quill.sources.USER) getQuill().setContents([], Quill.sources.USER)
} },
}) })
break break
} }
@ -330,7 +462,7 @@ const onSendTextEvent = lodash.throttle((value) => {
type: 'text', type: 'text',
content: data.items[0].content, content: data.items[0].content,
quote_id: data.quoteId, quote_id: data.quoteId,
mentions: data.mentionUids mentions: data.mentionUids,
} }
onSendMessage(message) onSendMessage(message)
@ -340,7 +472,7 @@ const onSendTextEvent = lodash.throttle((value) => {
const onInputEvent = ({ data }) => { const onInputEvent = ({ data }) => {
talkStore.updateItem({ talkStore.updateItem({
index_name: indexName.value, index_name: indexName.value,
draft_text: data draft_text: data,
}) })
// 线 // 线
@ -365,7 +497,13 @@ const evnets = {
}, },
} }
const { loadConfig, records, onLoad, onRefreshLoad, onJumpMessage } = useTalkRecord(talkParams.uid) const {
loadConfig,
records,
onLoad,
onRefreshLoad,
onJumpMessage,
} = useTalkRecord(talkParams.uid)
const getQuill = () => { const getQuill = () => {
return editor.value?.getQuill() return editor.value?.getQuill()
@ -384,11 +522,10 @@ const isShowCopy = (item) => {
default: default:
return false return false
} }
} }
const selectedMessage = computed(() => { const selectedMessage = computed(() => {
return virtualList.value.filter(item => item.isCheck) return virtualList.value.filter((item) => item.isCheck)
}) })
// //
@ -421,7 +558,7 @@ const onEmoticonEvent = (data) => {
quill.setSelection(index + 1, 0, 'user') quill.setSelection(index + 1, 0, 'user')
} else { } else {
let fn = emitCall('emoticon_event', data.value, () => { }) let fn = emitCall('emoticon_event', data.value, () => {})
emit('editor-event', fn) emit('editor-event', fn)
} }
} }
@ -434,7 +571,7 @@ const onEditorChange = () => {
if (!isEmptyDelta(delta)) { if (!isEmptyDelta(delta)) {
editorDraftStore.items[indexName.value || ''] = JSON.stringify({ editorDraftStore.items[indexName.value || ''] = JSON.stringify({
text: text, text: text,
ops: delta.ops ops: delta.ops,
}) })
} else { } else {
// editorDraftStore.items // editorDraftStore.items
@ -452,7 +589,7 @@ const onClipboardMatcher = (node, Delta) => {
if (op.insert && typeof op.insert === 'string') { if (op.insert && typeof op.insert === 'string') {
ops.push({ ops.push({
insert: op.insert, // insert: op.insert, //
attributes: {} // attributes: {}, //
}) })
} else { } else {
ops.push(op) ops.push(op)
@ -469,16 +606,16 @@ const editorOption = {
toolbar: false, toolbar: false,
clipboard: { clipboard: {
// //
matchers: [[Node.ELEMENT_NODE, onClipboardMatcher]] matchers: [[Node.ELEMENT_NODE, onClipboardMatcher]],
}, },
keyboard: { keyboard: {
bindings: { bindings: {
enter: { enter: {
key: 13, key: 13,
handler: onSendMessageClick handler: onSendMessageClick,
} },
} },
}, },
// imageUploader: { // imageUploader: {
@ -527,49 +664,49 @@ const virtualListChange = (vList) => {
} }
const onContextMenu = (menuType, item) => { const onContextMenu = (menuType, item) => {
console.log(menuType, item, 'item'); console.log(menuType, item, 'item')
switch (menuType) { switch (menuType) {
case 'actionCopy': case 'actionCopy':
actionCopy(item) actionCopy(item)
break; break
case 'multipleChoose': case 'multipleChoose':
multipleChoose(item) multipleChoose(item)
break; break
case 'actionCite': case 'actionCite':
actionCite(item) actionCite(item)
break; break
case 'actionWithdraw': case 'actionWithdraw':
actionWithdraw(item) actionWithdraw(item)
break; break
case 'actionDelete': case 'actionDelete':
actionDelete(item) actionDelete(item)
break; break
default: default:
break; break
} }
} }
const actionCopy = (item) => { const actionCopy = (item) => {
console.log('复制'); console.log('复制')
let content = '' let content = ''
switch (item.msg_type) { switch (item.msg_type) {
case 1: case 1:
content = item.extra.content content = item.extra.content
break; break
case 3: case 3:
content = item.extra.url content = item.extra.url
break; break
case 5: case 5:
content = item.extra.url content = item.extra.url
break; break
default: default:
break; break
} }
uni.setClipboardData({ uni.setClipboardData({
data: content, data: content,
}); })
} }
const multipleChoose = (item) => { const multipleChoose = (item) => {
@ -578,23 +715,23 @@ const multipleChoose = (item) => {
} }
const actionCite = (item) => { const actionCite = (item) => {
console.log('引用'); console.log('引用')
} }
const actionWithdraw = (item) => { const actionWithdraw = (item) => {
console.log('撤回'); console.log('撤回')
state.value.onfocusItem = item state.value.onfocusItem = item
state.value.showWin = true; state.value.showWin = true
} }
const withdrawerConfirm = () => { const withdrawerConfirm = () => {
dialogueStore.ApiRevokeRecord(state.value.onfocusItem.msg_id) dialogueStore.ApiRevokeRecord(state.value.onfocusItem.msg_id)
state.value.onfocusItem = null state.value.onfocusItem = null
state.value.showWin = false; state.value.showWin = false
} }
const actionDelete = (item) => { const actionDelete = (item) => {
console.log('删除'); console.log('删除')
item.isCheck = true item.isCheck = true
handleDelete() handleDelete()
} }
@ -603,14 +740,14 @@ const handleMergeForward = () => {
if (selectedMessage.value.length == 0) { if (selectedMessage.value.length == 0) {
return message.warning('未选择消息') return message.warning('未选择消息')
} }
console.log('合并转发'); console.log('合并转发')
dialogueStore.setForwardType(2) dialogueStore.setForwardType(2)
dialogueStore.setForwardMessages(selectedMessage.value) dialogueStore.setForwardMessages(selectedMessage.value)
uni.navigateTo({ uni.navigateTo({
url: '/pages/chooseChat/index', url: '/pages/chooseChat/index',
success: function (res) { success: function (res) {
clearMultiSelect() clearMultiSelect()
} },
}) })
} }
@ -618,14 +755,14 @@ const handleSingleForward = () => {
if (selectedMessage.value.length == 0) { if (selectedMessage.value.length == 0) {
return message.warning('未选择消息') return message.warning('未选择消息')
} }
console.log('逐条转发'); console.log('逐条转发')
dialogueStore.setForwardType(1) dialogueStore.setForwardType(1)
dialogueStore.setForwardMessages(selectedMessage.value) dialogueStore.setForwardMessages(selectedMessage.value)
uni.navigateTo({ uni.navigateTo({
url: '/pages/chooseChat/index', url: '/pages/chooseChat/index',
success: function (res) { success: function (res) {
clearMultiSelect() clearMultiSelect()
} },
}) })
} }
@ -633,40 +770,42 @@ const handleWechatForward = () => {
if (selectedMessage.value.length == 0) { if (selectedMessage.value.length == 0) {
return message.warning('未选择消息') return message.warning('未选择消息')
} }
console.log('微信转发'); console.log('微信转发')
} }
const handleDelete = () => { const handleDelete = () => {
if (selectedMessage.value.length == 0) { if (selectedMessage.value.length == 0) {
return message.warning('未选择消息') return message.warning('未选择消息')
} }
console.log('删除'); console.log('删除')
showConfirm({ showConfirm({
content: '确定删除聊天记录', content: '确定删除聊天记录',
confirmText:'删除', confirmText: '删除',
confirmColor:'#CF3050', confirmColor: '#CF3050',
onConfirm: async () => { onConfirm: async () => {
const msgIds = selectedMessage.value.map(item => item.msg_id) const msgIds = selectedMessage.value.map((item) => item.msg_id)
virtualList.value = virtualList.value.filter(item => !msgIds.includes(item.msg_id)) virtualList.value = virtualList.value.filter(
(item) => !msgIds.includes(item.msg_id),
)
dialogueStore.ApiDeleteRecord(msgIds) dialogueStore.ApiDeleteRecord(msgIds)
clearMultiSelect() clearMultiSelect()
}, },
onCancel: () => { onCancel: () => {},
}
}) })
} }
watch(
watch(() => zpagingRef.value, (newValue, oldValue) => { () => zpagingRef.value,
if (newValue) { (newValue, oldValue) => {
updateZpagingRef(newValue) if (newValue) {
} updateZpagingRef(newValue)
}) }
},
)
const clearMultiSelect = () => { const clearMultiSelect = () => {
dialogueStore.setMultiSelect(false) dialogueStore.setMultiSelect(false)
virtualList.value.forEach(item => { virtualList.value.forEach((item) => {
item.isCheck = false item.isCheck = false
}) })
} }
@ -689,14 +828,22 @@ const initData = async () => {
// //
const toChatSettingsPage = () => { const toChatSettingsPage = () => {
uni.navigateTo({ uni.navigateTo({
url: '/pages/chatSettings/index?groupId=' + talkParams?.receiver_id + '&sessionId=' + state.sessionId url:
'/pages/chatSettings/index?groupId=' +
talkParams?.receiver_id +
'&sessionId=' +
state.sessionId,
}) })
} }
// //
const toUserDetailPage = (userItem) => { const toUserDetailPage = (userItem) => {
uni.navigateTo({ uni.navigateTo({
url: '/pages/dialog/dialogDetail/userDetail?erpUserId=' + userItem.erp_user_id, url:
'/pages/dialog/dialogDetail/userDetail?erpUserId=' +
userItem.erp_user_id +
'&user_id=' +
userItem.user_id,
}) })
} }
@ -717,7 +864,7 @@ page {
.outer-layer { .outer-layer {
flex: 1; flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png"); background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover; background-size: cover;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -825,7 +972,7 @@ page {
margin-bottom: 6rpx; margin-bottom: 6rpx;
font-size: 24rpx; font-size: 24rpx;
user-select: none; user-select: none;
color: #BABABA; color: #bababa;
opacity: 1; opacity: 1;
&.show { &.show {
@ -970,7 +1117,7 @@ page {
:deep(.ql-editor) { :deep(.ql-editor) {
padding: 14rpx 22rpx; padding: 14rpx 22rpx;
background-color: #F9F9F9; background-color: #f9f9f9;
border-radius: 8rpx; border-radius: 8rpx;
outline: none !important; outline: none !important;
max-height: 294rpx; max-height: 294rpx;
@ -1011,6 +1158,6 @@ page {
.divider { .divider {
width: 100%; width: 100%;
height: 1rpx; height: 1rpx;
background-color: #E7E7E7; background-color: #e7e7e7;
} }
</style> </style>

View File

@ -11,12 +11,22 @@
:maxCount="99" :maxCount="99"
color="#D03050" color="#D03050"
> >
<tm-image <avatarModule
:width="96" :mode="props?.data?.group_type === 0 ? 1 : 2"
:height="96" :avatar="props?.data?.avatar"
:round="12" :groupType="props?.data?.group_type"
:src="avatarCpt" :userName="props?.data?.name"
></tm-image> :customStyle="{
width: '96rpx',
height: '96rpx',
}"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
</tm-badge> </tm-badge>
</div> </div>
<div class="chatInfo"> <div class="chatInfo">
@ -54,9 +64,10 @@
@click="handleTop" @click="handleTop"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#F09F1F] flex items-center justify-center" class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#F09F1F] flex items-center justify-center"
> >
{{ props.data.is_top === 1 ? "取消置顶" : "置顶" }} {{ props.data.is_top === 1 ? '取消置顶' : '置顶' }}
</div> </div>
<div <div
@click="handleDelete"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#CF3050] flex items-center justify-center" class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#CF3050] flex items-center justify-center"
> >
删除 删除
@ -71,20 +82,17 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, defineProps,computed } from "vue"; import avatarModule from '@/components/avatar-module/index.vue'
import dayjs from "dayjs"; import { ref, reactive, defineProps, computed } from 'vue'
import { beautifyTime } from "@/utils/datetime"; import dayjs from 'dayjs'
import { ServeClearTalkUnreadNum } from "@/api/chat"; import { beautifyTime } from '@/utils/datetime'
import { useTalkStore, useDialogueStore } from "@/store"; import { ServeClearTalkUnreadNum } from '@/api/chat'
import { useSessionMenu } from "@/hooks"; import { useTalkStore, useDialogueStore } from '@/store'
import groupDepartment from "@/static/image/chatList/groupDepartment.png"; import { useSessionMenu } from '@/hooks'
import groupProject from "@/static/image/chatList/groupProject.png";
import groupNormal from "@/static/image/chatList/groupNormal.png";
import groupCompany from "@/static/image/chatList/groupCompany.png";
const talkStore = useTalkStore(); const talkStore = useTalkStore()
const { onToTopTalk } = useSessionMenu(); const { onToTopTalk, onRemoveTalk } = useSessionMenu()
const dialogueStore = useDialogueStore(); const dialogueStore = useDialogueStore()
const props = defineProps({ const props = defineProps({
data: { data: {
type: Object, type: Object,
@ -96,35 +104,12 @@ const props = defineProps({
default: -1, default: -1,
required: true, required: true,
}, },
}); })
const avatarCpt = computed(() => {
let avatar = null;
if (props.data.avatar !== "") {
avatar = props.data.avatar;
} else {
switch (props.data.group_type) {
case 1:
avatar = groupNormal;
break;
case 2:
avatar = groupDepartment;
break;
case 3:
avatar = groupProject;
break;
case 4:
avatar = groupCompany;
break;
}
}
return avatar;
});
const cellClick = () => { const cellClick = () => {
console.log(props.data); console.log(props.data)
// //
dialogueStore.setDialogue(props.data); dialogueStore.setDialogue(props.data)
// //
if (props.data.unread_num > 0) { if (props.data.unread_num > 0) {
@ -135,18 +120,24 @@ const cellClick = () => {
talkStore.updateItem({ talkStore.updateItem({
index_name: props.data.index_name, index_name: props.data.index_name,
unread_num: 0, unread_num: 0,
}); })
}); })
} }
uni.navigateTo({ uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + props.data.id, url: '/pages/dialog/index?sessionId=' + props.data.id,
}); })
}; }
const handleTop = () => { const handleTop = () => {
console.log(props.data, 1); console.log(props.data, 1)
onToTopTalk(props.data); onToTopTalk(props.data)
}; }
//
const handleDelete = () => {
console.log(props.data)
onRemoveTalk(props.data)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chatItem { .chatItem {
@ -161,11 +152,6 @@ const handleTop = () => {
} }
} }
.avatarImg {
height: 96rpx;
width: 96rpx;
}
.chatInfo { .chatInfo {
flex: 1; flex: 1;
margin-left: 20rpx; margin-left: 20rpx;

View File

@ -25,8 +25,13 @@
:src="addCircle" :src="addCircle"
></tm-image> ></tm-image>
<template v-slot:label> <template v-slot:label>
<div class="w-full h-[208rpx] pt-[22rpx] pb-[22rpx] pl-[34rpx] pr-[32rpx]" > <div
<div @click="creatGroupChat" class="flex items-center mb-[30rpx]" > class="w-full h-[208rpx] pt-[22rpx] pb-[22rpx] pl-[34rpx] pr-[32rpx]"
>
<div
@click="creatGroupChat"
class="flex items-center mb-[30rpx]"
>
<div class="mr-[26rpx] flex items-center"> <div class="mr-[26rpx] flex items-center">
<tm-image <tm-image
:width="40" :width="40"
@ -34,10 +39,17 @@
:src="cahtPopover" :src="cahtPopover"
></tm-image> ></tm-image>
</div> </div>
<div class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold" >发起群聊</div> <div
class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold"
>
发起群聊
</div>
</div> </div>
<div class="divider" ></div> <div class="divider"></div>
<div class="flex items-center mt-[28rpx]" > <div
@click="toAddressBookPage"
class="flex items-center mt-[28rpx]"
>
<div class="mr-[26rpx] flex items-center"> <div class="mr-[26rpx] flex items-center">
<tm-image <tm-image
:width="40" :width="40"
@ -45,7 +57,9 @@
:src="zu3289" :src="zu3289"
></tm-image> ></tm-image>
</div> </div>
<div class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold" > <div
class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold"
>
通讯录 通讯录
</div> </div>
</div> </div>
@ -78,24 +92,25 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, watch, computed } from "vue"; import { ref, watch, computed } from 'vue'
import { onShow, onLoad } from "@dcloudio/uni-app"; import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from "@/store/chatList/index.js"; import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from "@/store/auth"; import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore } from "@/store"; import { useTalkStore, useUserStore, useDialogueStore } from '@/store'
import chatItem from "./components/chatItem.vue"; import chatItem from './components/chatItem.vue'
import addCircle from "@/static/image/chatList/addCircle.png"; import addCircle from '@/static/image/chatList/addCircle.png'
import cahtPopover from "@/static/image/chatList/cahtPopover.png"; import cahtPopover from '@/static/image/chatList/cahtPopover.png'
import zu3289 from "@/static/image/chatList/zu3289@2x.png"; import zu3289 from '@/static/image/chatList/zu3289@2x.png'
const talkStore = useTalkStore(); const talkStore = useTalkStore()
const userStore = useUserStore(); const userStore = useUserStore()
const { userInfo } = useAuth(); const dialogueStore = useDialogueStore()
const { userInfo } = useAuth()
const topItems = computed(() => talkStore.topItems); const topItems = computed(() => talkStore.topItems)
const items = computed(() => { const items = computed(() => {
// if (searchKeyword.value.length === 0) { // if (searchKeyword.value.length === 0) {
return talkStore.talkItems; return talkStore.talkItems
// } // }
// return talkStore.talkItems.filter((item) => { // return talkStore.talkItems.filter((item) => {
@ -103,31 +118,64 @@ const items = computed(() => {
// return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1 // return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
// }) // })
}); })
const creatGroupChat = () => { const creatGroupChat = () => {
uni.navigateTo({ uni.navigateTo({
url: "/pages/creatGroupChat/index", url: '/pages/creatGroupChat/index',
}); })
}; }
const toSearchPage = () => { const toSearchPage = () => {
uni.navigateTo({ uni.navigateTo({
url: "/pages/search/index", url: '/pages/search/index',
}); })
}; }
//
const toAddressBookPage = () => {
uni.navigateTo({
url: '/pages/addressBook/index',
})
}
watch( watch(
() => talkStore, () => talkStore,
(newValue, oldValue) => { (newValue, oldValue) => {
console.log(talkStore); // console.log(talkStore)
}, },
{ deep: true, immediate: true } { deep: true, immediate: true },
); )
onShow(() => { onShow(() => {
talkStore.loadTalkList(); talkStore.loadTalkList()
}); })
onLoad((options) => {
if (options?.openSessionIndex || options?.openSessionIndex === 0) {
if (items?.value?.length > 0) {
items.value.forEach((openSession,index) => {
if (index === Number(options?.openSessionIndex)) {
dialogueStore.setDialogue(openSession)
if (openSession.unread_num > 0) {
ServeClearTalkUnreadNum({
talk_type: openSession.talk_type,
receiver_id: openSession.receiver_id,
}).then(() => {
talkStore.updateItem({
index_name: openSession.index_name,
unread_num: 0,
})
})
}
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + openSession.id,
})
}
})
}
}
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
uni-page-body, uni-page-body,
@ -137,7 +185,7 @@ page {
.outer-layer { .outer-layer {
overflow-y: auto; overflow-y: auto;
flex: 1; flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png"); background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover; background-size: cover;
padding: 0 32rpx 20rpx 32rpx; padding: 0 32rpx 20rpx 32rpx;
display: flex; display: flex;
@ -155,14 +203,14 @@ page {
margin-top: 20rpx; margin-top: 20rpx;
background-color: #fff; background-color: #fff;
} }
.divider{ .divider {
height: 1rpx; height: 1rpx;
background-color: #7C7C7C; background-color: #7c7c7c;
opacity: 0.6; opacity: 0.6;
} }
.popoverBox { .popoverBox {
:deep(.popover-bcc){ :deep(.popover-bcc) {
transform:translateX(20rpx) translateY(40rpx); transform: translateX(20rpx) translateY(40rpx);
} }
} }
</style> </style>

View File

@ -4,6 +4,7 @@ import {
ServeRevokeRecords, ServeRevokeRecords,
ServePublishMessage, ServePublishMessage,
ServeCollectEmoticon, ServeCollectEmoticon,
ServeEmptyMessage,
} from '@/api/chat/index' } from '@/api/chat/index'
import { ServeGetGroupMembers } from '@/api/group/index' import { ServeGetGroupMembers } from '@/api/group/index'
import { useEditorStore } from './editor' import { useEditorStore } from './editor'
@ -123,7 +124,8 @@ export const useDialogueStore = defineStore('dialogue', {
key: o.key, key: o.key,
erp_user_id: o.erp_user_id, erp_user_id: o.erp_user_id,
is_mute: o.is_mute, is_mute: o.is_mute,
is_mine: useAuth()?.userInfo?.value?.ID === o?.erp_user_id ? true : false, is_mine:
useAuth()?.userInfo?.value?.ID === o?.erp_user_id ? true : false,
})) }))
}, },
@ -213,6 +215,18 @@ export const useDialogueStore = defineStore('dialogue', {
}) })
}, },
//清空聊天记录
apiClearRecord() {
ServeEmptyMessage({
talk_type: this.talk.talk_type,
receiver_id: this.talk.receiver_id,
}).then((res) => {
if (res.code == 200) {
} else {
}
})
},
// 撤销聊天记录 // 撤销聊天记录
ApiRevokeRecord(msg_id = '') { ApiRevokeRecord(msg_id = '') {
ServeRevokeRecords({ msg_id }).then((res) => { ServeRevokeRecords({ msg_id }).then((res) => {

View File

@ -109,6 +109,18 @@ export const useDialogueListStore = createGlobalState(() => {
item.records = item.records.filter(item=>!msgIds.includes(item.msg_id)) item.records = item.records.filter(item=>!msgIds.includes(item.msg_id))
} }
//删除会话时同时刪除storage中存儲的會話
const delDialogueStorage = (indexName) =>{
if(dialogueList?.value?.length > 0){
dialogueList.value.forEach((item, index) => {
if (item?.index_name === indexName) {
dialogueList.value.splice(index, 1)
}
})
}
}
return { return {
dialogueList, dialogueList,
zpagingRef, zpagingRef,
@ -122,5 +134,6 @@ export const useDialogueListStore = createGlobalState(() => {
addChatRecord, addChatRecord,
virtualList, virtualList,
batchDelDialogueRecord, batchDelDialogueRecord,
delDialogueStorage
} }
}) })

View File

@ -10,7 +10,7 @@ export const useTalkStore = defineStore('talk', {
// 加载状态[1:未加载;2:加载中;3:加载完成;4:加载失败;] // 加载状态[1:未加载;2:加载中;3:加载完成;4:加载失败;]
loadStatus: 2, loadStatus: 2,
// 会话列表 // 会话列表
items: [] items: [],
} }
}, },
getters: { getters: {
@ -26,14 +26,14 @@ export const useTalkStore = defineStore('talk', {
// 对话列表 // 对话列表
talkItems: (state) => { talkItems: (state) => {
let topList = state.items.filter((item) => item.is_top == 1) let topList = state.items.filter((item) => item.is_top == 1)
let listT = state.items.filter(v=>v.is_top !== 1) let listT = state.items.filter((v) => v.is_top !== 1)
let listP = topList.sort((a, b) => { let listP = topList.sort((a, b) => {
return ttime(b.updated_at) - ttime(a.updated_at) return ttime(b.updated_at) - ttime(a.updated_at)
}) })
listT = listT.sort((a, b) => { listT = listT.sort((a, b) => {
return ttime(b.updated_at) - ttime(a.updated_at) return ttime(b.updated_at) - ttime(a.updated_at)
}) })
return [...listP,...listT] return [...listP, ...listT]
}, },
// 消息未读数总计 // 消息未读数总计
@ -41,7 +41,7 @@ export const useTalkStore = defineStore('talk', {
return state.items.reduce((total, item) => { return state.items.reduce((total, item) => {
return total + item.unread_num return total + item.unread_num
}, 0) }, 0)
} },
}, },
actions: { actions: {
findItem(index_name) { findItem(index_name) {
@ -50,7 +50,9 @@ export const useTalkStore = defineStore('talk', {
// 更新对话节点 // 更新对话节点
updateItem(params) { updateItem(params) {
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) item && Object.assign(item, params)
}, },
@ -73,7 +75,9 @@ export const useTalkStore = defineStore('talk', {
// 更新对话消息 // 更新对话消息
updateMessage(params) { updateMessage(params) {
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
@ -83,7 +87,9 @@ export const useTalkStore = defineStore('talk', {
// 更新联系人备注 // 更新联系人备注
setRemark(params) { setRemark(params) {
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) item && (item.remark = params.remark)
}, },
@ -126,34 +132,31 @@ export const useTalkStore = defineStore('talk', {
return this.items.findIndex((item) => item.index_name === index_name) return this.items.findIndex((item) => item.index_name === index_name)
}, },
toTalk(talk_type, receiver_id, router) { toTalk(talk_type, receiver_id, erp_user_id) {
const route = {
path: '/message',
query: {
v: new Date().getTime()
}
}
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) >= 0) { if (this.findTalkIndex(`${talk_type}_${receiver_id}`) >= 0) {
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`) sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
return router.push(route) uni.reLaunch({
url: '/pages/index/index?openSessionIndex=' + this.findTalkIndex(`${talk_type}_${receiver_id}`),
})
return
} }
ServeCreateTalkList({ ServeCreateTalkList({
talk_type, talk_type,
receiver_id erp_user_id,
}).then(({ code, data, message }) => { }).then(({ code, data, message }) => {
if (code == 200) { if (code == 200) {
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) { if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
this.addItem(formatTalkItem(data)) this.addItem(formatTalkItem(data))
} }
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`) sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
return router.push(route) uni.reLaunch({
url: '/pages/index/index?openSessionIndex=' + this.findTalkIndex(`${talk_type}_${receiver_id}`),
})
} else { } else {
message.warning(message) message.warning(message)
} }
}) })
} },
} },
}) })