chat-pc/src/views/message/inner/IndexSider.vue

1704 lines
49 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="tsx" setup>
import {
computed,
ref,
onMounted,
watch,
reactive,
onBeforeMount,
getCurrentInstance,
h,
nextTick
} from 'vue'
import { onBeforeRouteUpdate } from 'vue-router'
import { useDialogueStore, useTalkStore, useUserStore } from '@/store'
import {
NDropdown,
NIcon,
NInput,
NPopover,
NTabs,
NTab,
NCard,
NButton,
NPagination
} from 'naive-ui'
import { Search, Plus, Right, AddOne, PeoplePlusOne } from '@icon-park/vue-next'
import TalkItem from './TalkItem.vue'
import Skeleton from './Skeleton.vue'
import { ServeClearTalkUnreadNum, ServeAddFriend, GetFriendList } from '@/api/chat'
import GroupLaunch from '@/components/group/GroupLaunch.vue'
import { getCacheIndexName } from '@/utils/talk'
import { ISession } from '@/types/chat'
import { useSessionMenu } from '@/hooks'
import customModal from '@/components/common/customModal.vue'
import xSearchForm from '@/components/x-naive-ui/x-search-form/index.vue'
import xNDataTable from '@/components/x-naive-ui/x-n-data-table/index.vue'
import flTree from '@/components/flnlayout/tree/flnindex.vue'
import { processError, processSuccess } from '@/utils/helper/message.js'
import chatAppSearchList from '@/components/search/searchList.vue'
import { ServeSeachQueryAll, ServeQueryTalkRecord, ServeUserGroupChatList } from '@/api/search'
import { GetContactFriendList } from '@/api/chat'
import { getUserInfoByERPUserId } from '@/api/user'
import HighlightText from '@/components/search/highLightText.vue'
import { useRouter } from 'vue-router'
import icon from '@/assets/image/chatList/addressBook.png'
import { useUtil } from '@/hooks/useUtil'
import UserCardModal from '@/components/user/UserCardModal.vue'
const { useMessage } = useUtil()
const router = useRouter()
const currentInstance = getCurrentInstance()
const $request = currentInstance?.appContext.config.globalProperties?.$request
const {
dropdown,
onContextMenuTalkHandle,
onContextMenu: onContextMenuTalk,
onCloseContextMenu,
onToTopTalk
} = useSessionMenu()
const dialogueStore = useDialogueStore()
const talkStore = useTalkStore()
const userStore = useUserStore()
const userParams = reactive({
uid: computed(() => userStore.uid)
})
const isShowGroup = ref(false)
const searchKeyword = ref('')
const topItems = computed((): ISession[] => talkStore.topItems)
const unreadNum = computed(() => talkStore.talkUnreadNum)
// 是否删除好友弹框
const handleConfirmDel = (row) => {
window['$dialog'].create({
title: '温馨提示',
content: '是否删除该好友?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
console.log('确定')
let params = {
receiver_id: row.id, //聊天的用户id
talk_type: 1
}
let url = '/api/v1/contact/friend/delete'
$request.HTTP.components.postDataByParams(url, params).then((res) => {
// console.log(res)
if (res?.code === 200) {
useMessage.success('删除成功')
getMyFriends()
}
})
}
})
}
const option = ref([
{
label: '添加好友',
key: 'addFriend',
icon: () => <n-icon size="20" component={PeoplePlusOne} />
},
{
label: '通讯录',
key: 'addressBook',
icon: () => <img style="width: 19px; height: 20px; cursor: pointer" src={icon} />
}
])
//自定义搜索
const renderChatAppSearch = () => {
return h(
chatAppSearchList,
{
searchResultPageSize: 3,
listLimit: true,
apiRequest: ServeSeachQueryAll,
searchText: searchKeyword.value,
onClickSearchItem: (searchText, searchResultKey, talk_type, receiver_id, res) => {
console.log(searchText, searchResultKey, talk_type, receiver_id)
const result = JSON.parse(decodeURIComponent(res))
console.log(result)
if (searchResultKey === 'general_infos') {
state.ServeQueryTalkRecordParams = encodeURIComponent(
JSON.stringify({
talk_type: 0, //1私聊2群聊
receiver_id: 0, //查详情的时候需传入
last_group_id: 0, //最后一条群id
last_member_id: 0, //最后一条用户id
last_receiver_user_name: '', //最后一条用户名
last_receiver_group_name: '' //最后一条群名
})
)
state.isShowSearchRecordModal = true
state.searchRecordText = searchText
state.selectItemInList = res
} else {
if (searchResultKey === 'user_infos') {
talk_type = 1
}
if (searchResultKey === 'combinedGroup') {
talk_type = 2
}
talkStore.toTalk(talk_type, receiver_id, router)
}
state.showSearchDropdown = false
},
onToMoreResultPage: (searchResultKey, searchText) => {
if (searchResultKey === 'general_infos') {
state.ServeQueryTalkRecordParams = encodeURIComponent(
JSON.stringify({
talk_type: 0, //1私聊2群聊
receiver_id: 0, //查详情的时候需传入
last_group_id: 0, //最后一条群id
last_member_id: 0, //最后一条用户id
last_receiver_user_name: '', //最后一条用户名
last_receiver_group_name: '' //最后一条群名
})
)
state.isShowSearchRecordModal = true
state.searchRecordText = searchText
}
console.log(searchResultKey, searchText)
state.showSearchDropdown = false
}
},
{
'result-title': ({ getResultKeysValue, searchResultKey, searchResultIndex }) => {
return h(
'div',
{
style: {
padding: searchResultIndex === 0 ? '6px 10px 5px' : '18px 10px 5px',
borderBottom: '1px solid #f8f8f8'
}
},
[
h(
'span',
{
class: 'text-[14px] font-regular',
style: 'line-height: 20px; color: #999999;'
},
getResultKeysValue(searchResultKey)
)
]
)
}
}
)
}
const state = reactive({
userInfo: {
isShowUserCardModal: false,
user_id: NaN,
erp_user_id: NaN
},
isShowAddressBookModal: false, // 是否显示通讯录模态框
isShowAddFriendModal: false, // 是否显示添加好友模态框
customModalStyle: {
width: '1288px',
height: '846px',
backgroundColor: '#F9F9FD'
}, //自定义模态框样式
addressBookSearchConfig: [
{
label: '姓名',
key: 'nickName',
type: 'input',
valueType: 'string'
}
], // 通讯录搜索配置
groupChatListSearchConfig: [
{
label: '群聊名称',
key: 'groupName',
type: 'input',
valueType: 'string'
}
],
// 我的好友搜索配置
myFriendSearchConfig: [
{
label: '好友名称',
key: 'myFriendname',
type: 'input',
valueType: 'string'
}
],
addFriendSearchConfig: [
{
label: '姓名',
key: 'friendName',
type: 'input',
valueType: 'string'
}
], // 添加好友搜索配置
// 群聊列表搜索配置
treeData: [],
expandedKeys: [],
clickKey: 3,
treeRefreshCount: 0,
treeSelectData: {},
addressBookColumns: [
{
title: '姓名 【工号】',
field: 'nickname',
width: 200,
ellipsis: {
tooltip: true
},
render(row, index) {
// return row.nickname + '【' + row.job_num + '】'
return row.nickName + '【' + row.jobNum + '】'
}
},
{
title: '岗位名称',
field: 'user_position',
width: 400,
ellipsis: {
tooltip: true
},
render(row, index) {
let positionNames = Array.isArray(row.depPositions)
? row.depPositions.flatMap((dep) =>
Array.isArray(dep.positions) ? dep.positions.map((pos) => pos.name) : []
)
: []
return positionNames.join(' , ')
// return row.user_position.map((item) => item.position_name).join(' , ')
}
},
{
title: '操作',
field: 'action',
width: 180,
align: 'center',
fixed: 'right',
render(row, index) {
return h(
NButton,
{
size: 'small',
text: true,
color: '#46299d',
onClick: () => handleEnterChat(row)
},
{ default: () => '进入聊天' }
)
}
}
], // 通讯录表格列
groupChatListColumns: [
{
title: '群聊名称',
field: 'groupName',
width: 400,
ellipsis: {
tooltip: true
},
render(row, index) {
return row.group_name
}
},
{
title: '群类型',
field: 'groupType',
width: 200,
ellipsis: true,
render(row, index) {
let groupType = row.group_type
if (groupType == 1) {
return '普通群'
} else if (groupType == 2) {
return '部门群'
} else if (groupType == 3) {
return '项目群'
} else if (groupType == 4) {
return '公司群'
}
}
},
{
title: '操作',
field: 'action',
width: 180,
align: 'center',
fixed: 'right',
render(row, index) {
return h(
NButton,
{
size: 'small',
text: true,
color: '#46299d',
onClick: () => handleEnterChat(row)
},
{ default: () => '进入聊天' }
)
}
}
], // 群聊列表表格列
myFriendListColumns: [
{
title: '姓名 【工号】',
field: 'nickname',
width: 200,
ellipsis: {
tooltip: true
},
render(row, index) {
return row.nickname + '【' + row.job_num + '】'
}
},
{
title: '岗位名称',
field: 'user_position',
width: 400,
ellipsis: {
tooltip: true
},
render(row, index) {
// let positionNames = Array.isArray(row.user_position)
// ? row.depPositions.flatMap((dep) =>
// Array.isArray(dep.positions) ? dep.positions.map((pos) => pos.name) : []
// )
// : []
return row.user_position.map((item) => item.position_name).join(' , ')
}
},
{
title: '操作',
field: 'action',
width: 180,
align: 'center',
fixed: 'right',
render(row, index) {
return [
h(
NButton,
{
size: 'small',
text: true,
color: '#46299d',
onClick: () => handleEnterChat(row)
},
{ default: () => '进入聊天' }
),
h(
NButton,
{
size: 'small',
text: true,
color: '#46299d',
class: 'pl-[10px]',
onClick: () => handleConfirmDel(row)
},
{ default: () => '删除好友' }
)
]
}
}
], // 我的好友表格列
addFriendListColumns: [
{
title: '姓名 【工号】',
field: 'nickname',
width: 200,
ellipsis: {
tooltip: true
},
render(row, index) {
return row.nickname + '【' + row.job_num + '】'
}
},
{
title: '岗位名称',
field: 'user_position',
width: 400,
ellipsis: {
tooltip: true
},
render(row, index) {
// let positionNames = Array.isArray(row.user_position)
// ? row.depPositions.flatMap((dep) =>
// Array.isArray(dep.positions) ? dep.positions.map((pos) => pos.name) : []
// )
// : []
return row.user_position.map((item) => item.position_name).join(' , ')
}
},
{
title: '操作',
field: 'action',
width: 180,
align: 'center',
fixed: 'right',
render(row, index) {
return h(
NButton,
{
size: 'small',
text: true,
color: '#46299d',
onClick: () => {
state.userInfo.user_id = row.id
state.userInfo.erp_user_id = row.erp_user_id
state.userInfo.isShowUserCardModal = true
}
},
{ default: () => '查看' }
)
}
}
], // 添加表格列
addressBookData: [], // 通讯录表格数据
company_name: '', // 当前公司别
groupChatListData: [], // 群聊列表表格数据
myFriendListData: [], // 我的好友表格数据
addFriendList: [], // 搜索出来的可添加好友
addressBookTableHeight: 524, // 通讯录表格高度
addressBookTableWidth: 800, // 通讯录表格宽度
addressBookPage: 1, // 通讯录表格页码
addressBookPageSize: 10, // 通讯录表格每页条数
addressBookTotal: 0, // 通讯录表格总条数
addressBookSearchNickName: '', // 通讯录搜索条件-姓名
addressBookCurrentTab: 'employeeAddressBook', // 通讯录当前tab
groupChatListPage: 1, // 群聊列表表格页码
groupChatListPageSize: 10, // 群聊列表表格每页条数
groupChatListTotal: 0, // 群聊列表表格总条数
groupChatListSearchGroupName: '', // 群聊列表搜索条件-群聊名称
myFriendListPage: 1, // 我的好友表格页码
myFriendListPageSize: 10, // 我的好友表格每页条数
myFriendListTotal: 0, // 我的好友表格总条数
myFriendListSearchName: '', // 我的好友搜索条件-好友名称
chatSearchOptions: [
{
key: 'chatSearch',
type: 'render',
render: renderChatAppSearch
}
], // 聊天搜索选项
isShowSearchRecordModal: false, // 是否显示搜索聊天记录模态框
customSearchRecordModalStyle: {
width: '997px',
height: '740px',
backgroundColor: '#F9F9FD'
}, //自定义模态框样式
searchRecordText: '', // 搜索聊天记录文本
ServeQueryTalkRecordParams: '', // 搜索聊天记录参数
ServeQueryTalkRecordDetailParams: '', // 搜索聊天记录详情参数
isShowSearchRecordDetailInfo: false, // 是否显示搜索聊天记录详情
// 拆分 searchList 和 searchDetailList 独立状态
searchList: {
searchText: '',
apiParams: '',
lastId: undefined as any
},
searchDetailList: {
searchText: '',
apiParams: '',
lastId: undefined as any,
total: 0
},
showSearchDropdown: false, // 是否显示搜索下拉框
selectItemInList: '' // 在列表选中的聊天记录搜索项
})
const items = computed((): ISession[] => {
let filtered = talkStore.talkItems
if (searchKeyword.value.length > 0) {
filtered = filtered.filter((item: ISession) => {
let keyword = item.remark || item.name
return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
})
}
// 置顶和非置顶分组
const topItems = filtered
.filter((item) => item.is_top === 1)
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
const normalItems = filtered.filter((item) => item.is_top !== 1)
return [...topItems, ...normalItems]
})
setTimeout(() => {
console.log('items', items)
}, 2000)
watch(
() => state.addressBookSearchNickName,
(newValue, oldValue) => {
// console.log(newValue, 'newValue')
if (newValue) {
state.addressBookTableWidth = 1142
state.addressBookPage = 1
} else {
state.addressBookTableWidth = 800
state.clickKey = 3
state.treeRefreshCount++
state.addressBookPage = 1
}
getDepPoisUser()
}
)
watch(
() => state.myFriendListSearchName,
(newValue, oldValue) => {
if (newValue) {
state.myFriendListPage = 1
} else {
state.myFriendListPage = 1
}
getMyFriends()
}
)
watch(
() => state.groupChatListSearchGroupName,
(newValue, oldValue) => {
if (newValue) {
state.groupChatListPage = 1
} else {
state.groupChatListPage = 1
}
getUserGroupChatList()
}
)
// 监听搜索关键字变化,重置所有相关状态
watch(
() => state.searchRecordText,
(newVal, oldVal) => {
// 重置左侧
state.searchList.searchText = newVal
state.searchList.apiParams = encodeURIComponent(
JSON.stringify({
talk_type: 0,
receiver_id: 0,
last_group_id: 0,
last_member_id: 0,
last_receiver_user_name: '',
last_receiver_group_name: ''
})
)
state.searchList.lastId = undefined
// 重置右侧
state.searchDetailList.searchText = newVal
state.searchDetailList.apiParams = ''
state.searchDetailList.lastId = undefined
// 关闭右侧详情
state.isShowSearchRecordDetailInfo = false
}
)
// 列表加载状态
const loadStatus = computed(() => talkStore.loadStatus)
// 当前会话索引
const indexName = computed(() => dialogueStore.index_name)
// 切换会话
const onTabTalk = (item: ISession, follow = false) => {
console.log('onTabTalk')
console.log('item.index_name === indexName.value', item.index_name === indexName.value)
if (item.index_name === indexName.value) return
if (dialogueStore.isOpenMultiSelect) {
//切换会话时,如果多选模式开启,则关闭多选模式
dialogueStore.closeMultiSelect()
}
searchKeyword.value = ''
dialogueStore.isManualSwitch = true
// 更新编辑信息
dialogueStore.setDialogue(item)
// 清空消息未读数
if (item.unread_num > 0) {
ServeClearTalkUnreadNum({
talk_type: item.talk_type,
receiver_id: item.receiver_id
}).then(() => {
talkStore.updateItem({
index_name: item.index_name,
unread_num: 0,
atsign_num: 0
})
})
}
// 设置滚动条跟随
if (follow) {
const el = document.getElementById('talk-session-list')
if (el) {
let index = talkStore.findTalkIndex(item.index_name)
el.scrollTo({
top: index * 66 + index * 5,
behavior: 'smooth'
})
}
}
}
const onReload = () => {
talkStore.loadTalkList()
}
// 初始化加载
const onInitialize = () => {
let index_name = getCacheIndexName()
console.log('index_name', index_name)
index_name && onTabTalk(talkStore.findItem(index_name), true)
}
// 路由更新事件
onBeforeRouteUpdate(onInitialize)
onBeforeMount(() => {
getTreeData()
getDepPoisUser()
// getMyFriends()
getUserGroupChatList()
})
onMounted(() => {
onInitialize()
})
// 点击显示通讯录模态框
const showAddressBookModal = () => {
state.isShowAddressBookModal = true
}
// 点击显示添加好友模态框
const showAddFriendModal = () => {
state.isShowAddFriendModal = true
}
const handleSelect = (key: string | number) => {
if (key === 'addressBook') return showAddressBookModal()
showAddFriendModal()
}
// 点击关闭通讯录模态框
const closeAddressBookModal = () => {
state.isShowAddressBookModal = false
resetAddressBookModal()
}
// 点击关闭添加好友模态框
const closeAddFriendModal = () => {
state.isShowAddFriendModal = false
resetAddressBookModal()
}
const handleTreeClick = ({ selectedKey, tree }) => {
// console.log(tree)
state.clickKey = tree.key
state.treeSelectData = tree
state.addressBookPage = 1
getDepPoisUser()
}
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID
item.label = item.name
item.title = item.name
if (item.sons) {
item.children = item.sons
calcTreeData(item.children)
}
delete item.ID
delete item.name
delete item.sons
}
}
// 获取组织树数据-已隐藏
const getTreeData = () => {
let url = '/department/v2/tree/filter'
let params = {}
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes
calcTreeData(data)
state.treeData = data
state.treeRefreshCount++
getDepPoisUser()
} else {
processError(res.msg || '获取失败!')
}
},
() => {
processError('获取失败!')
},
() => {
processError('获取失败!')
}
)
}
// 获取我的好友
const getMyFriends = () => {
// myFriendListPage: 1, // 我的好友表格页码
// myFriendListPageSize: 10, // 我的好友表格每页条数
// myFriendListTotal: 0, // 我的好友表格总条数
// myFriendListSearchName: '', // 我的好友搜索条件-好友名称
let params = {
type: 'myFriends', //查我得好友的时候写死myFriends
page: state.myFriendListPage,
page_size: state.myFriendListPageSize,
name: state.myFriendListSearchName
}
// let url = '/api/v1/contact/friend/list'
GetContactFriendList(params).then((res) => {
// console.log(res)
if (res.code === 200 && Array.isArray(res.data.user_list)) {
state.myFriendListData = res.data.user_list || []
state.company_name = res.data.company_name || ''
state.myFriendListTotal = res.data.count
}
})
}
// 获取部门下的人员
const getDepPoisUser = () => {
let url = '/user/v2/list'
// let url = '/api/v1/contact/friend/list'
let params = {
departmentId: state.addressBookSearchNickName ? undefined : state.clickKey,
page: state.addressBookPage,
pageSize: state.addressBookPageSize,
status: 'notactive',
nickName: state.addressBookSearchNickName
}
$request.HTTP.components.postDataByParams(url, params).then((res) => {
// console.log(res)
if (res.code === 200) {
state.addressBookData = res?.data?.data || []
state.addressBookTotal = res?.data?.count || 0
}
})
// let params = {
// type: 'addressBook', //查我的通讯录的时候写死addressBook
// page: state.addressBookPage,
// page_size: state.addressBookPageSize,
// name: state.addressBookSearchNickName
// }
// GetContactFriendList(params).then((res) => {
// // console.log(res)
// if (res.code === 200 && Array.isArray(res.data.user_list)) {
// state.addressBookData = res.data.user_list || []
// state.company_name = res.data.company_name || ''
// state.addressBookTotal = res.data.count
// }
// })
}
// 搜索可添加好友
const AddFriends = (row) => {
let params = {
receiver_id: row.erp_user_id, //聊天的用户id
talk_type: 1
}
ServeAddFriend(params).then((res) => {
if (res?.code === 200) {
useMessage.success('添加成功')
}
})
}
//点击进入对应的聊天
const handleEnterChat = async (row) => {
console.log(row)
if (
state.addressBookCurrentTab === 'employeeAddressBook' ||
state.addressBookCurrentTab === 'myFriend'
) {
//员工通讯录,聊天类型一定为单聊
await getUserInfoByERPUserId({ erp_user_id: row.ID }).then((res) => {
// console.log(res)
if (res.code === 200) {
let sysUserInfo = res.data
talkStore.toTalk(1, sysUserInfo.sys_id, router)
}
})
} else if (state.addressBookCurrentTab === 'groupChatList') {
//群聊列表,聊天类型一定为群聊
talkStore.toTalk(2, row.id, router)
}
state.isShowAddressBookModal = false
resetAddressBookModal()
}
//恢复默认通讯录模态框
const resetAddressBookModal = () => {
nextTick(() => {
state.addressBookCurrentTab = 'employeeAddressBook'
state.addressBookSearchNickName = ''
state.groupChatListSearchGroupName = ''
state.addressBookTableWidth = 800
state.clickKey = 3
state.treeRefreshCount++
state.addressBookPage = 1
state.addressBookPageSize = 10
state.groupChatListPage = 1
state.groupChatListPageSize = 10
getDepPoisUser()
// getMyFriends()
getUserGroupChatList()
state.addFriendList = []
})
}
//处理页数变化
const handleAddressBookPagination = (page) => {
state.addressBookPage = page
getDepPoisUser()
}
//处理每页条数变化
const handleAddressBookPaginationSize = (pageSize) => {
state.addressBookPageSize = pageSize
state.addressBookPage = 1
getDepPoisUser()
}
//处理通讯录搜索
const changeAddressBookSearch = (value) => {
if (!value.nickName?.trim()) {
state.addressBookSearchNickName = ''
} else {
state.addressBookSearchNickName = value.nickName
}
}
//处理通讯录tab切换
const handleAddressBookTabChange = (value) => {
console.log(value, 'value')
state.addressBookCurrentTab = value
}
//处理群聊列表搜索
const changeGroupChatListSearch = (value) => {
console.log(value, 'value')
if (!value.groupName?.trim()) {
state.groupChatListSearchGroupName = ''
} else {
state.groupChatListSearchGroupName = value.groupName
}
}
//处理我的好友搜索
const changeMyFriendListSearch = (value) => {
console.log(value, 'value')
if (!value.myFriendname?.trim()) {
state.myFriendListSearchName = ''
} else {
state.myFriendListSearchName = value.myFriendname
}
}
const changeAddFriendSearch = (value) => {
console.log(11)
if (value.friendName?.trim()) {
state.myFriendListSearchName = ''
// 搜索好友
let params = {
name: value.friendName
}
// let url = '/api/v1/contact/friend/search'
GetFriendList(params).then((res) => {
// console.log(res)
if (res.code === 200) {
state.addFriendList = res.data?.user_list || []
}
})
}
}
//获取用户所在群聊列表
const getUserGroupChatList = () => {
let params = {
page: state.groupChatListPage,
page_size: state.groupChatListPageSize,
group_name: state.groupChatListSearchGroupName
}
ServeUserGroupChatList(params).then((res) => {
// console.log(res)
if (res.code === 200) {
state.groupChatListData = res?.data?.items || []
state.groupChatListTotal = res?.data?.total || 0
}
})
}
//处理群聊列表页数变化
const handleGroupChatListPagination = (value) => {
console.log(value, 'value')
state.groupChatListPage = value
getUserGroupChatList()
}
//处理群聊列表每页条数变化
const handleGroupChatListPaginationSize = (value) => {
console.log(value, 'value')
state.groupChatListPageSize = value
state.groupChatListPage = 1
getUserGroupChatList()
}
//处理我的好友页数变化
const handleMyFriendListPagination = (value) => {
console.log(value, 'value')
state.myFriendListPage = value
getMyFriends()
}
//处理我的好友每页条数变化
const handleMyFriendListPaginationSize = (value) => {
console.log(value, 'value')
state.myFriendListPageSize = value
state.myFriendListPage = 1
getMyFriends()
}
//处理搜索聊天记录点击
const handleClickSearchItem = (searchText, searchResultKey, talk_type, receiver_id, res) => {
console.log(searchText, searchResultKey, talk_type, receiver_id)
// const result = JSON.parse(decodeURIComponent(res))
// console.log(result)
if (searchResultKey === 'general_infos') {
// 先清空右侧
state.isShowSearchRecordDetailInfo = false
state.searchDetailList.apiParams = encodeURIComponent(
JSON.stringify({
last_group_id: 0,
last_member_id: 0,
receiver_id: receiver_id,
talk_type: talk_type
})
)
state.searchDetailList.searchText = state.searchRecordText
state.searchDetailList.lastId = undefined
// 再显示
nextTick(() => {
state.isShowSearchRecordDetailInfo = true
})
}
}
//处理点击搜索结果item
const handleClickSearchResultItem = (searchText, searchResultKey, talk_type, receiver_id, res) => {
const result = JSON.parse(decodeURIComponent(res))
console.error(result, 'result')
let receiver_id_ = receiver_id
//单聊如果receiver_id为当前用户id则使用user_id
if(talk_type === 1 && receiver_id === userParams.uid){
receiver_id_ = result.user_id
}
// 根据搜索结果, 指定用于查询指定消息上下文的sequence
dialogueStore.specifiedMsg = encodeURIComponent(
JSON.stringify({
talk_type,
receiver_id: receiver_id_,
msg_id: result.msg_id,
cursor: result.sequence - 15 > 0 ? result.sequence - 15 : 0,
direction: 'down',
sort_sequence: 'asc',
create_time: result.created_at
})
)
console.error(dialogueStore.specifiedMsg, 'dialogueStore.specifiedMsg')
talkStore.toTalk(talk_type, receiver_id_, router)
state.isShowSearchRecordModal = false
state.searchRecordText = ''
searchKeyword.value = ''
}
//处理点击停留item变化
const handleClickStayItemChange = (item) => {
if (item) {
state.isShowSearchRecordDetailInfo = true
} else {
state.isShowSearchRecordDetailInfo = false
}
}
// 定义搜索列表组件的ref
const searchListRef = ref()
// 定义搜索详情列表组件的ref
const searchDetailListRef = ref()
// lastIdChange 事件区分来源
const handleSearchListLastIdChange = (
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name
) => {
state.searchList.lastId = {
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name
}
state.searchList.apiParams = encodeURIComponent(
JSON.stringify({
...JSON.parse(decodeURIComponent(state.searchList.apiParams)),
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name
})
)
}
const handleSearchDetailListLastIdChange = (last_id, last_group_id, last_member_id) => {
state.searchDetailList.lastId = { last_id, last_group_id, last_member_id }
state.searchDetailList.apiParams = encodeURIComponent(
JSON.stringify({
...JSON.parse(decodeURIComponent(state.searchDetailList.apiParams)),
last_id,
last_group_id,
last_member_id
})
)
}
// 关闭搜索聊天记录模态框
const handleCloseSearchRecordModal = () => {
state.isShowSearchRecordModal = false
state.searchRecordText = ''
}
// 获取搜索结果总数
const getResultTotalCount = (total) => {
state.searchDetailList.total = total
}
// 进入搜索结果聊天
const handleEnterSearchResultChat = () => {
const searchResult = JSON.parse(decodeURIComponent(state.searchDetailList.apiParams))
talkStore.toTalk(searchResult.talk_type, searchResult.receiver_id, router)
state.isShowSearchRecordModal = false
state.searchRecordText = ''
searchKeyword.value = ''
}
</script>
<template>
<!-- 右键菜单 -->
<n-dropdown
class="dropdown-menus"
:show="dropdown.show"
:x="dropdown.x"
:y="dropdown.y"
:options="dropdown.options"
@select="onContextMenuTalkHandle"
@clickoutside="onCloseContextMenu"
/>
<section class="el-container is-vertical height100">
<!-- 工具栏目 -->
<header class="el-header header-tools">
<n-dropdown
trigger="click"
:options="state.chatSearchOptions"
style="width: 248px; height: 677px"
:show="state.showSearchDropdown"
@clickoutside="state.showSearchDropdown = false"
>
<n-input
placeholder="搜索好友 / 群聊"
v-model:value.trim="searchKeyword"
clearable
style="width: 78%"
@click="state.showSearchDropdown = true"
>
<!-- <template #prefix>
<n-icon :component="Search" />
</template> -->
</n-input>
</n-dropdown>
<!-- <n-button circle @click="isShowGroup = true">
<template #icon>
<n-icon :component="Plus" />
</template>
</n-button> -->
<img
style="width: 19px; height: 20px; cursor: pointer"
src="@/assets/image/chatList/addressBook.png"
alt=""
@click="showAddressBookModal"
/>
<!-- <n-dropdown :options="option" @select="handleSelect">
<n-button> <n-icon :component="AddOne" /></n-button>
</n-dropdown> -->
</header>
<!-- 置顶栏目 -->
<!-- <header class="el-header header-top" v-show="loadStatus == 3 && topItems.length > 0">
<n-popover v-for="item in topItems" :key="item.index_name" placement="bottom" trigger="hover">
<template #trigger>
<div
class="top-item pointer"
@click="onTabTalk(item, true)"
:class="{
active: item.index_name == indexName
}"
>
<im-avatar :src="item.avatar" :size="34" :username="item.name" />
<span class="icon-mark robot" v-show="item.is_robot == 1"> 助 </span>
<span class="icon-mark group" v-show="item.talk_type == 2 && item.is_robot == 0">
</span>
<span class="text">{{ item.remark || item.name }}</span>
</div>
</template>
<span> {{ item.remark || item.name }} </span>
</n-popover>
</header> -->
<!-- 标题栏目 -->
<!-- <header
v-show="loadStatus == 3 && talkStore.talkItems.length > 0"
class="el-header header-badge"
:class="{ shadow: false }"
>
<p>会话记录({{ talkStore.talkItems.length }})</p>
<p>
<span class="badge unread" v-show="unreadNum">{{ unreadNum }}未读</span>
</p>
</header> -->
<main id="talk-session-list" class="el-main me-scrollbar me-scrollbar-thumb">
<template v-if="loadStatus == 2"><Skeleton /></template>
<template v-else>
<n-virtual-list :item-size="64" :items="items">
<template #default="{ item }">
<TalkItem
:key="item.index_name + item.unread_num"
:data="item"
:avatar="item.avatar"
:username="item.remark || item.name"
:active="item.index_name == indexName"
@tab-talk="onTabTalk"
@top-talk="onToTopTalk"
@contextmenu.prevent="onContextMenuTalk($event, item)"
/>
</template>
</n-virtual-list>
</template>
</main>
</section>
<GroupLaunch v-if="isShowGroup" @close="isShowGroup = false" @on-submit="onReload" />
<UserCardModal
v-model:show="state.userInfo.isShowUserCardModal"
v-model:uid="(state.userInfo as any).user_id"
:euid="(state.userInfo as any).erp_user_id"
@update:send="closeAddFriendModal"
/>
<customModal
v-model:show="state.isShowAddressBookModal"
title="通讯录"
:style="state.customModalStyle"
:customCloseBtn="true"
:closable="false"
:customCloseEvent="true"
@customCloseModal="closeAddressBookModal"
>
<template #content>
<div class="custom-modal-content">
<n-card style="padding: 0 12px">
<n-tabs
type="line"
@update:value="handleAddressBookTabChange"
tab-style="font-size: 16px; font-weight: 600;color: #8B8B8B;"
>
<!-- <n-tab name="employeeAddressBook">组织架构</n-tab>
<n-tab name="employeeAddressBook">我的好友</n-tab> -->
<!-- <n-tab name="employeeAddressBook">组织架构</n-tab> -->
<n-tab name="employeeAddressBook">员工通讯录</n-tab>
<!-- <n-tab name="myFriend">我的好友</n-tab> -->
<n-tab name="groupChatList">群聊列表</n-tab>
</n-tabs>
<xSearchForm
v-if="state.addressBookCurrentTab == 'employeeAddressBook'"
:search-config="state.addressBookSearchConfig"
customInputPlaceholder="请输入姓名"
@change="changeAddressBookSearch"
:cols="3"
></xSearchForm>
<xSearchForm
v-if="state.addressBookCurrentTab == 'groupChatList'"
:search-config="state.groupChatListSearchConfig"
customInputPlaceholder="请输入群聊名称"
@change="changeGroupChatListSearch"
:cols="3"
></xSearchForm>
<xSearchForm
v-if="state.addressBookCurrentTab == 'myFriend'"
:search-config="state.myFriendSearchConfig"
customInputPlaceholder="请输入好友名称"
@change="changeMyFriendListSearch"
:cols="3"
></xSearchForm>
<p
v-if="state.addressBookCurrentTab === 'employeeAddressBook'"
style="transform: translateY(-10px)"
>
{{ state.company_name }}
</p>
<div
class="addressBook-content"
v-if="state.addressBookCurrentTab == 'employeeAddressBook'"
>
<!-- 隐藏组织架构树 v-if="!state.addressBookSearchNickName && 0"-->
<div class="addressBook-tree" v-if="!state.addressBookSearchNickName">
<fl-tree
:data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
@triggerTreeClick="handleTreeClick"
></fl-tree>
</div>
<div class="addressBook-table">
<xNDataTable
:columns="state.addressBookColumns"
:data="state.addressBookData"
:style="{
height: `${state.addressBookTableHeight}px`,
width: `${state.addressBookTableWidth}px`
}"
flex-height
></xNDataTable>
<div class="addressBook-pagination">
<n-pagination
v-model:page="state.addressBookPage"
v-model:page-size="state.addressBookPageSize"
:item-count="state.addressBookTotal"
show-quick-jumper
show-size-picker
:page-sizes="[10, 20, 50]"
:on-update:page="handleAddressBookPagination"
:on-update:page-size="handleAddressBookPaginationSize"
>
<template #prefix="{ itemCount }"> 共 {{ itemCount }} 条记录 </template>
</n-pagination>
</div>
</div>
</div>
<!-- 我的好友 -->
<div class="groupChatList-content" v-if="state.addressBookCurrentTab == 'myFriend'">
<div class="groupChatList-table">
<xNDataTable
:columns="state.myFriendListColumns"
:data="state.myFriendListData"
:style="{
height: '523px',
width: '1148px'
}"
flex-height
></xNDataTable>
<div class="groupChatList-pagination">
<n-pagination
v-model:page="state.myFriendListPage"
v-model:page-size="state.myFriendListPageSize"
:item-count="state.myFriendListTotal"
show-quick-jumper
show-size-picker
:page-sizes="[10, 20, 50]"
:on-update:page="handleMyFriendListPagination"
:on-update:page-size="handleMyFriendListPaginationSize"
>
<template #prefix="{ itemCount }"> 共 {{ itemCount }} 条记录 </template>
</n-pagination>
</div>
</div>
</div>
<div class="groupChatList-content" v-if="state.addressBookCurrentTab == 'groupChatList'">
<div class="groupChatList-table">
<xNDataTable
:columns="state.groupChatListColumns"
:data="state.groupChatListData"
:style="{
height: '523px',
width: '1148px'
}"
flex-height
></xNDataTable>
<div class="groupChatList-pagination">
<n-pagination
v-model:page="state.groupChatListPage"
v-model:page-size="state.groupChatListPageSize"
:item-count="state.groupChatListTotal"
show-quick-jumper
show-size-picker
:page-sizes="[10, 20, 50]"
:on-update:page="handleGroupChatListPagination"
:on-update:page-size="handleGroupChatListPaginationSize"
>
<template #prefix="{ itemCount }"> 共 {{ itemCount }} 条记录 </template>
</n-pagination>
</div>
</div>
</div>
</n-card>
</div>
</template>
</customModal>
<customModal
v-model:show="state.isShowAddFriendModal"
title="添加好友"
:style="state.customModalStyle"
:customCloseBtn="true"
:closable="false"
:customCloseEvent="true"
@customCloseModal="closeAddFriendModal"
>
<template #content>
<div class="custom-modal-content">
<n-card style="padding: 0 12px">
<xSearchForm
:search-config="state.addFriendSearchConfig"
customInputPlaceholder="请输入姓名"
@change="changeAddFriendSearch"
:cols="3"
></xSearchForm>
<div class="groupChatList-content">
<div class="groupChatList-table">
<xNDataTable
:columns="state.addFriendListColumns"
:data="state.addFriendList"
:style="{
height: '523px',
width: '1148px'
}"
flex-height
></xNDataTable>
</div>
</div>
</n-card>
</div>
</template>
</customModal>
<customModal
v-model:show="state.isShowSearchRecordModal"
title="搜索聊天记录"
:style="state.customSearchRecordModalStyle"
:customCloseBtn="true"
:closable="false"
:customCloseEvent="true"
@customCloseModal="handleCloseSearchRecordModal"
>
<template #content>
<div class="search-record-modal-content">
<n-card style="padding: 0 12px">
<div class="search-record-input">
<span class="search-record-input-title">搜索</span>
<n-input
type="text"
v-model:value="state.searchRecordText"
placeholder="请输入"
clearable
>
<template #clear-icon>
<img src="@/assets/image/icon/close-btn-grey-line.png" alt="close" />
</template>
</n-input>
</div>
<div class="search-record-card" v-if="state.searchRecordText">
<div class="search-record-list">
<chatAppSearchList
ref="searchListRef"
:searchResultPageSize="10"
:listLimit="false"
:apiRequest="ServeQueryTalkRecord"
:apiParams="state.searchList.apiParams"
:searchText="state.searchList.searchText"
:isPagination="true"
searchResultKey="general_infos"
@clickSearchItem="handleClickSearchItem"
:useClickStay="true"
@clickStayItemChange="handleClickStayItemChange"
@lastIdChange="handleSearchListLastIdChange"
:searchResultMaxHeight="'517px'"
:selectItemInList="state.selectItemInList"
></chatAppSearchList>
</div>
<div class="search-record-detail">
<div class="search-record-detail-header" v-if="state.isShowSearchRecordDetailInfo">
<HighlightText
class="text-[14px] text-[#B0B0B0] leading-[20px]"
:text="
state.searchDetailList.total +
'条与“' +
state.searchRecordText +
'”相关的搜索结果'
"
:searchText="state.searchRecordText"
/>
<div class="search-record-detail-header-btn" @click="handleEnterSearchResultChat">
<span>进入聊天</span>
<n-icon :component="Right" color="#46299D" size="14px" />
</div>
</div>
<chatAppSearchList
ref="searchDetailListRef"
v-if="state.isShowSearchRecordDetailInfo"
:searchResultPageSize="10"
:listLimit="false"
:apiRequest="ServeQueryTalkRecord"
:apiParams="state.searchDetailList.apiParams"
:searchText="state.searchDetailList.searchText"
:isPagination="true"
:searchRecordDetail="true"
@lastIdChange="handleSearchDetailListLastIdChange"
:searchResultMaxHeight="'469px'"
@resultTotalCount="getResultTotalCount"
@clickSearchItem="handleClickSearchResultItem"
></chatAppSearchList>
</div>
</div>
<div class="search-record-empty" v-if="!state.searchRecordText">
<img src="@/assets/image/chatList/search-empty.png" alt="" />
<span>暂无搜索内容</span>
</div>
</n-card>
</div>
</template>
</customModal>
</template>
<style lang="less" scoped>
.header-tools {
height: 60px;
flex-shrink: 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-around;
padding: 0 8px;
}
.header-badge {
height: 38px;
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 10px;
&.shadow {
box-shadow: 0 2px 6px 0 rgb(31 35 41 / 5%);
}
.unread {
background-color: #ff4d4f;
color: white;
cursor: pointer;
}
}
.header-top {
padding: 5px 8px;
padding-right: 0;
padding-right: 8px;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
grid-gap: 0 14px;
grid-template-columns: repeat(auto-fill, 32px);
display: grid;
box-sizing: border-box;
.top-item {
flex-basis: 46px;
flex-shrink: 0;
height: 56px;
margin: 3px 2px 3px 2px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
position: relative;
.icon-mark {
position: absolute;
height: 25px;
width: 25px;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
right: -12px;
bottom: 15px;
transform: scale(0.6);
border-radius: 50%;
&.group {
color: #3370ff;
background-color: #e1eaff;
}
&.robot {
color: #dc9b04 !important;
background-color: #faf1d1 !important;
}
}
&.active {
.text {
color: rgb(80 138 254);
}
}
.text {
display: inline-block;
height: 20px;
font-size: 12px;
transform: scale(0.9);
text-align: center;
line-height: 20px;
word-break: break-all;
overflow: hidden;
}
}
}
html[theme-mode='dark'] {
.header-badge {
&.shadow {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
}
}
}
.custom-modal-content {
box-sizing: border-box;
width: 100%;
padding: 0 12px;
:deep(.n-tabs-tab--active) {
color: #46299d !important;
}
.addressBook-content {
display: flex;
flex-direction: row;
gap: 20px;
.addressBook-tree {
width: 328px;
height: 524px;
overflow: auto;
border: 1px solid #efeff5;
border-radius: 4px;
padding: 12px 20px;
box-sizing: border-box;
}
.addressBook-table {
:deep(.n-data-table-th) {
background-color: #46299d;
color: #fff;
}
.addressBook-pagination {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 22px 0 0;
box-sizing: border-box;
}
}
}
.groupChatList-content {
display: flex;
flex-direction: row;
gap: 20px;
.groupChatList-table {
:deep(.n-data-table-th) {
background-color: #46299d;
color: #fff;
}
.groupChatList-pagination {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 22px 0 0;
box-sizing: border-box;
}
}
}
}
.search-record-modal-content {
box-sizing: border-box;
width: 100%;
padding: 0 12px;
:deep(.n-card) {
border: 0;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);
}
.search-record-input {
display: flex;
align-items: center;
justify-content: center;
.search-record-input-title {
width: 78px;
}
}
.search-record-card {
display: flex;
flex-direction: row;
gap: 20px;
padding: 28px 0 0;
.search-record-list {
width: 260px;
height: 517px;
border: 1px solid #efeff5;
}
.search-record-detail {
width: 578px;
height: 517px;
border: 1px solid #efeff5;
.search-record-detail-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 4px 14px 10px;
box-sizing: border-box;
.search-record-detail-header-btn {
line-height: 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 8px;
cursor: pointer;
span {
line-height: 20px;
color: #46299d;
font-size: 14px;
font-weight: 400;
}
}
}
}
}
.search-record-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 28px 0 0;
height: 519px;
img {
width: 160px;
height: 104px;
}
span {
font-size: 14px;
color: #999;
font-weight: 400;
margin: 13px 0 0;
}
}
}
:deep(.n-data-table .n-data-table-tr:not(.n-data-table-tr--summary):hover > .n-data-table-td) {
background-color: rgba(70, 41, 157, 0.1) !important;
}
</style>