@ -1,46 +1,168 @@
< script lang = " t s" setup >
import { ref , computed , onMounted , watch } from 'vue'
< script lang = " j s" setup >
import { ref , computed , onMounted , watch , reactive , nextTick , getCurrentInstance , h } from 'vue'
import { ServeGetTalkList } from '@/api/chat.js'
import { ServeGetGroups } from '@/api/group'
import XNModal from '@/components/x-naive-ui/x-n-modal/index.vue'
const emit = defineEmits ( [ 'close' , 'on-submit' ] )
import { Plus } from '@vicons/tabler'
import { CloseCircle } from '@vicons/ionicons5'
interface Item {
id : number
type : number
name : string
avatar : string
remark : string
checked : boolean
keyword : string
}
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 { ServeUserGroupChatList } from '@/api/search'
import { GetContactFriendList } from '@/api/chat'
import { getUserInfoByERPUserId } from '@/api/user'
import { useRouter } from 'vue-router'
import { useUtil } from '@/hooks/useUtil'
import { NButton } from 'naive-ui'
const emit = defineEmits ( [ 'close' , 'on-submit' ] )
const { useMessage } = useUtil ( )
const router = useRouter ( )
const currentInstance = getCurrentInstance ( )
const $request = currentInstance ? . appContext . config . globalProperties ? . $request
const isShowBox = defineModel ( 'show' )
const loading = ref ( true )
const items = ref < Item [ ] > ( [ ] )
const items = ref ([ ] )
const keywords = ref ( '' )
const loadGroupStatus = ref ( false )
defineProps < {
forwardMode : number
} > ( )
/ / 搜 索 过 滤 器 : 不 再 按 类 型 过 滤 , 将 好 友 和 群 组 融 合 在 一 起
const forwardMode = defineProps ( [ 'forwardMode' ] )
/ / 通 讯 录 弹 窗 相 关 状 态
const state = reactive ( {
selectedGroupRowKeys : [ ] ,
isShowAddressBookModal : false ,
customModalStyle : {
width : '1288px' ,
height : '846px' ,
backgroundColor : '#F9F9FD'
} ,
addressBookSearchConfig : [
{
label : '姓名' ,
key : 'nickName' ,
type : 'input' ,
valueType : 'string'
}
] ,
groupChatListSearchConfig : [
{
label : '群聊名称' ,
key : 'groupName' ,
type : 'input' ,
valueType : 'string'
}
] ,
treeData : [ ] ,
expandedKeys : [ ] ,
clickKey : 3 ,
treeRefreshCount : 0 ,
treeSelectData : { } ,
addressBookColumns : [
{
type : 'selection'
} ,
{
title : '姓名 【工号】' ,
field : 'nickname' ,
width : 200 ,
ellipsis : {
tooltip : true
} ,
render ( row , index ) {
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 ( ' , ' )
}
}
] ,
groupChatListColumns : [
{
type : 'selection'
} ,
{
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 '公司群'
}
}
}
] ,
addressBookData : [ ] ,
company _name : '' ,
groupChatListData : [ ] ,
addressBookTableHeight : 500 ,
addressBookTableWidth : 800 ,
addressBookPage : 1 ,
addressBookPageSize : 10 ,
addressBookTotal : 0 ,
addressBookSearchNickName : '' ,
addressBookCurrentTab : 'employeeAddressBook' ,
groupChatListPage : 1 ,
groupChatListPageSize : 10 ,
groupChatListTotal : 0 ,
groupChatListSearchGroupName : '' ,
selectedRowKeys : [ ]
} )
/ / 原 有 转 发 功 能 的 过 滤 器
const searchFilter = computed ( ( ) => {
return items . value . filter ( ( item : Item ) => {
return items . value . filter ( ( item ) => {
return item . name . toLowerCase ( ) . includes ( keywords . value . toLowerCase ( ) )
} )
} )
const checkedFilter = computed ( ( ) => {
return items . value . filter ( ( item : Item ) => item . checked )
return items . value . filter ( ( item ) => item . checked )
} )
const isCanSubmit = computed ( ( ) => {
return ! checkedFilter . value . length
} )
/ / 原 有 加 载 数 据 函 数
const onLoad = ( ) => {
onLoadContact ( )
/ / o n L o a d G r o u p ( )
}
const onLoadContact = ( ) => {
@ -50,7 +172,7 @@ const onLoadContact = () => {
if ( res . code == 200 ) {
let list = res . data . items || [ ]
items . value = list . filter ( ( item : any ) => ( ( item . talk _type === 1 && item . receiver _id !== 2 ) || item . talk _type !== 1 ) ) . map ( ( item : any ) => {
items . value = list . filter ( ( item ) => ( ( item . talk _type === 1 && item . receiver _id !== 2 ) || item . talk _type !== 1 ) ) . map ( ( item ) => {
return {
... item ,
checked : false
@ -63,57 +185,42 @@ const onLoadContact = () => {
} )
}
/ / c o n s t o n L o a d G r o u p = a s y n c ( ) = > {
/ / i f ( l o a d G r o u p S t a t u s . v a l u e ) {
/ / r e t u r n
/ / }
/ / l o a d i n g . v a l u e = t r u e
/ / l e t { c o d e , d a t a } = a w a i t S e r v e G e t G r o u p s ( )
/ / i f ( c o d e ! = 2 0 0 ) {
/ / l o a d i n g . v a l u e = f a l s e
/ / r e t u r n
/ / }
/ / l e t l i s t = d a t a . i t e m s . m a p ( ( i t e m : a n y ) = > {
/ / r e t u r n {
/ / i d : i t e m . i d ,
/ / a v a t a r : i t e m . a v a t a r ,
/ / t y p e : 2 ,
/ / n a m e : i t e m . g r o u p _ n a m e ,
/ / k e y w o r d : i t e m . g r o u p _ n a m e ,
/ / r e m a r k : ' ' ,
/ / c h e c k e d : f a l s e
/ / }
/ / } )
/ / i t e m s . v a l u e . p u s h ( . . . l i s t )
/ / l o a d i n g . v a l u e = f a l s e
/ / l o a d G r o u p S t a t u s . v a l u e = t r u e
/ / }
/ / 原 有 转 发 功 能 函 数
const onMaskClick = ( ) => {
emit ( 'close' )
}
const onTriggerContact = ( item : any ) => {
/ / 如 果 是 单 选 模 式 , 先 取 消 所 有 选 中
const selectType = ref ( 2 )
const onTriggerContact = ( item ) => {
const clicked = items . value . find ( ( val ) => val . id === item . id )
if ( ! clicked ) return
if ( selectType . value === 1 ) {
/ / 单 选 : 清 空 后 仅 选 中 当 前
items . value . forEach ( contact => {
contact . checked = false
} )
clicked . checked = true
return
}
let data = items . value . find ( ( val : any ) => val . id === item . id )
if ( data ) {
data . checked = ! data . checked
/ / 多 选 : 限 制 同 一 类 型 选 择
if ( ! clicked . checked ) {
const checked = items . value . filter ( c => c . checked )
const currentType = checked . length ? checked [ 0 ] . talk _type : null
if ( currentType && currentType !== clicked . talk _type ) {
/ / 切 换 类 型 时 清 空 已 选 , 保 持 单 一 类 型
items . value . forEach ( c => { if ( c . checked ) c . checked = false } )
}
clicked . checked = true
} else {
clicked . checked = false
}
}
const onRemoveContact = ( item : any ) => {
let data = items . value . find ( ( val : any ) => val . id === item . id )
const onRemoveContact = ( item ) => {
let data = items . value . find ( ( val ) => val . id === item . id )
if ( data ) {
data . checked = false
@ -125,7 +232,7 @@ const onCancel = () => {
}
const onSubmit = ( ) => {
let data = checkedFilter . value . map ( ( item : any ) => {
let data = checkedFilter . value . map ( ( item ) => {
return {
receiver _id : item . receiver _id ,
talk _type : item . talk _type
@ -134,17 +241,204 @@ const onSubmit = () => {
emit ( 'on-submit' , data )
}
/ / 1 单 选 2 多 选
const selectType = ref ( 1 )
const changeSelectType = ( ) => {
selectType . value = selectType . value == 1 ? 2 : 1
/ / 通 讯 录 弹 窗 功 能 函 数
const onCreateContact = ( ) => {
state . isShowAddressBookModal = true
getTreeData ( )
getDepPoisUser ( )
getUserGroupChatList ( )
}
/ / 切 换 选 择 模 式 时 清 空 已 选 择 的 联 系 人
items . value . forEach ( item => {
item . checked = false
const closeAddressBookModal = ( ) => {
state . isShowAddressBookModal = false
resetAddressBookModal ( )
}
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 getDepPoisUser = ( ) => {
let url = '/user/v2/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 ) => {
if ( res . code === 200 ) {
state . addressBookData = res ? . data ? . data || [ ]
state . addressBookTotal = res ? . data ? . count || 0
}
} )
}
const getUserGroupChatList = ( ) => {
let params = {
page : state . groupChatListPage ,
page _size : state . groupChatListPageSize ,
group _name : state . groupChatListSearchGroupName
}
ServeUserGroupChatList ( params ) . then ( ( res ) => {
if ( res . code === 200 ) {
state . groupChatListData = res ? . data ? . items || [ ]
state . groupChatListTotal = res ? . data ? . total || 0
}
} )
}
const handleTreeClick = ( { selectedKey , tree } ) => {
state . clickKey = tree . key
state . treeSelectData = tree
state . addressBookPage = 1
getDepPoisUser ( )
}
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
state . selectedRowKeys = [ ]
getDepPoisUser ( )
getUserGroupChatList ( )
} )
}
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
}
}
const handleAddressBookTabChange = ( value ) => {
state . addressBookCurrentTab = value
/ / 切 换 时 清 空 另 一 类 选 项
if ( value === 'employeeAddressBook' ) {
state . selectedGroupRowKeys = [ ]
} else if ( value === 'groupChatList' ) {
state . selectedRowKeys = [ ]
}
}
const changeGroupChatListSearch = ( value ) => {
if ( ! value . groupName ? . trim ( ) ) {
state . groupChatListSearchGroupName = ''
} else {
state . groupChatListSearchGroupName = value . groupName
}
}
const handleGroupChatListPagination = ( page ) => {
state . groupChatListPage = page
getUserGroupChatList ( )
}
const handleGroupChatListPaginationSize = ( pageSize ) => {
state . groupChatListPageSize = pageSize
state . groupChatListPage = 1
getUserGroupChatList ( )
}
const onAddressBookCancel = ( ) => {
state . isShowAddressBookModal = false
}
const onAddressBookSubmit = async ( ) => {
if ( state . addressBookCurrentTab === 'employeeAddressBook' ) {
if ( ! Array . isArray ( state . selectedRowKeys ) || state . selectedRowKeys . length === 0 ) {
processError ( '请选择联系人' )
return
}
try {
const results = await Promise . all (
state . selectedRowKeys . map ( ( erpId ) => getUserInfoByERPUserId ( { erp _user _id : erpId } ) )
)
const data = results
. filter ( ( res ) => res && res . code === 200 && res . data && res . data . sys _id )
. map ( ( res ) => ( { receiver _id : res . data . sys _id , talk _type : 1 } ) )
if ( data . length === 0 ) {
processError ( '未获取到有效联系人' )
return
}
emit ( 'on-submit' , data )
state . isShowAddressBookModal = false
} catch ( e ) {
processError ( '发送失败,请稍后重试' )
}
} else if ( state . addressBookCurrentTab === 'groupChatList' ) {
if ( ! Array . isArray ( state . selectedGroupRowKeys ) || state . selectedGroupRowKeys . length === 0 ) {
processError ( '请选择群聊' )
return
}
const data = state . selectedGroupRowKeys . map ( ( gid ) => ( { receiver _id : gid , talk _type : 2 } ) )
emit ( 'on-submit' , data )
state . isShowAddressBookModal = false
}
}
watch ( ( ) => {
return isShowBox . value
} , ( newVal ) => {
@ -152,12 +446,42 @@ if(newVal){
onLoad ( )
}
} )
watch ( ( ) => state . addressBookSearchNickName , ( newValue , oldValue ) => {
if ( newValue ) {
state . addressBookTableWidth = 1142
state . addressBookPage = 1
} else {
state . addressBookTableWidth = 800
state . clickKey = 3
state . treeRefreshCount ++
state . addressBookPage = 1
}
getDepPoisUser ( )
} )
watch ( ( ) => state . groupChatListSearchGroupName , ( newValue , oldValue ) => {
if ( newValue ) {
state . groupChatListPage = 1
} else {
state . groupChatListPage = 1
}
getUserGroupChatList ( )
} )
< / script >
< template >
< x -n -modal v -model :show ="isShowBox" : title = "forwardMode === 2 ? '合并转发' : '逐条转发'" style = "width: 997px; height: 740px;background-color: #F9F9FD"
: on - after - leave = "onMaskClick" content - style = "display: flex; justify-content: center; align-items: center;" >
< div class = "w-927px h-627px bg-#fff rounded-3px px-35px py-20px" >
< div class = "w-927px h-627px bg-#fff rounded-3px px-35px pb-20px pt-10px" >
< div class = "w-100% flex justify-end mb-5px" >
< n -button text type = "primary" @click ="onCreateContact" >
< template # icon >
< Plus / >
< / template >
创建新聊天
< / n - b u t t o n >
< / div >
< div class = "flex items-center justify-between mb-28px" >
< div class = "text-#333639" > 搜索 < / div >
< div class = "w-779px h-34px" >
@ -168,11 +492,11 @@ if(newVal){
< / div >
< div class = "flex justify-between" >
< div class = "w-260px h-517px rounded-4px border-1px border-solid border-#E5E5E5 px-12px" >
< div class = "border-b-2px border-b-solid border-b-#FBFBFB h-35px flex items-center justify-end" >
<!-- < div class = "border-b-2px border-b-solid border-b-#FBFBFB h-35px flex items-center justify-end" >
< n -button text color = "#46299D" class = "text-14px" @click ="changeSelectType" >
{ { selectType === 1 ? '多选' : '取消多选' } }
< / n - b u t t o n >
< / div >
< / div > -- >
< div >
< n -virtual -list v-if ="!loading" style="max-height: 470px" :item-size="65" :items ="searchFilter" >
< template # default = "{ item }" >
@ -259,5 +583,175 @@ if(newVal){
< / div >
< / div >
< / div >
< / x - n - m o d a l >
<!-- 通讯录弹窗 -- >
< 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 - t a b >
< n -tab name = "groupChatList" > 群聊列表 < / n - t a b >
< / n - t a b s >
< 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 >
< div class = "addressBook-content" v-if ="state.addressBookCurrentTab == 'employeeAddressBook'" >
< div class = "addressBook-tree" v-if ="!state.addressBookSearchNickName" >
< fl -tree
: data = "state.treeData"
: expandedKeys = "state.expandedKeys"
: refreshCount = "state.treeRefreshCount"
: clickKey = "state.clickKey"
@ triggerTreeClick = "handleTreeClick"
> < / f l - t r e e >
< / div >
< div class = "addressBook-table" >
< xNDataTable
: columns = "state.addressBookColumns"
: data = "state.addressBookData"
: style = " {
height : ` ${ state . addressBookTableHeight } px ` ,
width : ` ${ state . addressBookTableWidth } px `
} "
: row - key = "row => row.ID"
v - model : checked - row - keys = "state.selectedRowKeys"
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 - p a g i n a t i o n >
< / 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 : '500px' ,
width : '1148px'
} "
: row - key = "row => row.id"
v - model : checked - row - keys = "state.selectedGroupRowKeys"
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 - p a g i n a t i o n >
< / div >
< / div >
< / div >
<!-- 底部操作按钮 , 仅保留展示 -- >
< div style = "display: flex; justify-content: center; align-items: center; padding: 10px 0;" >
< n -button color = "#C7C7C9" class = "w-200px h-34px text-14px text-#fff mr-10px" @click ="onAddressBookCancel" > 取消 < / n -button >
< n -button color = "#46299D" class = "w-200px h-34px text-14px text-#fff" @click ="onAddressBookSubmit" > 发送 < / n -button >
< / div >
< / n - c a r d >
< / div >
< / template >
< / customModal >
< / template >
< style scoped lang = "scss" >
. custom - modal - content {
. addressBook - content {
display : flex ;
flex - direction : row ;
gap : 20 px ;
. addressBook - tree {
width : 328 px ;
height : 500 px ;
overflow : auto ;
border : 1 px solid # efeff5 ;
border - radius : 4 px ;
padding : 12 px 20 px ;
box - sizing : border - box ;
}
. addressBook - table {
: deep ( . n - data - table - th ) {
background - color : # 46299 d ;
color : # fff ;
}
. addressBook - pagination {
display : flex ;
justify - content : flex - end ;
align - items : center ;
padding : 22 px 0 0 ;
box - sizing : border - box ;
}
}
}
. groupChatList - content {
display : flex ;
flex - direction : row ;
gap : 20 px ;
. groupChatList - table {
: deep ( . n - data - table - th ) {
background - color : # 46299 d ;
color : # fff ;
}
. groupChatList - pagination {
display : flex ;
justify - content : flex - end ;
align - items : center ;
padding : 22 px 0 0 ;
box - sizing : border - box ;
}
}
}
}
< / style >