Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
foreign
/
customer-service
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
86932012
authored
Jul 13, 2021
by
panjiangyi
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
格式化代码
parent
03c9c8d6
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
179 additions
and
218 deletions
chat-list.vue
chat-room.vue
chat.vue
components/message.vue
components/who-read-list.vue
create-chat.vue
hybrid-input/index.vue
message-input.vue
service/request.ts
store/index.ts
store/model.ts
xim/index.ts
xim/xim.ts
chat-list.vue
View file @
86932012
...
...
@@ -58,10 +58,7 @@
</div>
</div>
</div>
<div
class=
"empty"
v-if=
"chatRooms && chatRooms.length
<
=
0
"
>
<div
class=
"empty"
v-if=
"chatRooms && chatRooms.length
<
=
0
"
>
{{
searchKeyword
?
"无相关接待"
:
"无接待"
}}
</div>
</el-scrollbar>
...
...
@@ -71,7 +68,7 @@
</
template
>
<
script
lang=
"ts"
>
import
buttonThrottle
from
"@/utils/button-throttle"
;
//
import buttonThrottle from "@/utils/button-throttle";
import
{
Component
,
Prop
,
Vue
}
from
"vue-property-decorator"
;
import
{
chatStore
,
ChatStore
}
from
"@/customer-service/store/model"
;
...
...
@@ -93,41 +90,41 @@ export function parserMessage(type: string, rawMsg: string) {
return
`[不支持的消息格式]`
;
}
}
type
Chat
=
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
[
"list"
][
number
]
type
Chat
=
NonNullable
<
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
>
[
"list"
][
number
];
@
Component
({
components
:
{}
})
export
default
class
ChatList
extends
Vue
{
@
chatStore
.
Action
(
ChatStore
.
ACTION_GET_MY_CHAT_LIST
)
private
readonly
getMyChatList
!
:
ChatStore
.
ACTION_GET_MY_CHAT_LIST
private
readonly
getMyChatList
!
:
ChatStore
.
ACTION_GET_MY_CHAT_LIST
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
)
private
readonly
currentChatUniplatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
private
readonly
currentChatUniplatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_VERSION
)
private
readonly
uniplatVersion
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_VERSION
private
readonly
uniplatVersion
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_VERSION
;
@
chatStore
.
State
(
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
)
private
readonly
chatList
!
:
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
private
readonly
chatList
!
:
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
)
private
readonly
saveChatId
!
:
ChatStore
.
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
private
readonly
saveChatId
!
:
ChatStore
.
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SAVE_MYSELF_ID
)
private
readonly
saveMyId
!
:
ChatStore
.
MUTATION_SAVE_MYSELF_ID
private
readonly
saveMyId
!
:
ChatStore
.
MUTATION_SAVE_MYSELF_ID
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SET_CHAT_SOURCE
)
private
readonly
setSource
!
:
ChatStore
.
MUTATION_SET_CHAT_SOURCE
private
readonly
setSource
!
:
ChatStore
.
MUTATION_SET_CHAT_SOURCE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SAVE_CHAT_TITLE
)
private
readonly
saveChatTitle
!
:
ChatStore
.
MUTATION_SAVE_CHAT_TITLE
private
readonly
saveChatTitle
!
:
ChatStore
.
MUTATION_SAVE_CHAT_TITLE
;
@
Prop
({
type
:
String
,
default
:
"-1"
})
private
selected
!
:
string
private
selected
!
:
string
;
private
searchKeyword
=
""
private
searchKeyword
=
""
;
private
get
chatRooms
()
{
return
this
.
chatList
?.
list
||
[];
...
...
@@ -169,7 +166,7 @@ export default class ChatList extends Vue {
});
}
@
buttonThrottle
()
//
@buttonThrottle()
private
async
search
()
{
this
.
searchKeyword
=
this
.
searchKeyword
.
trim
();
if
(
!
this
.
searchKeyword
)
{
...
...
@@ -183,10 +180,12 @@ export default class ChatList extends Vue {
if
(
this
.
currentChatUniplatId
===
data
.
uniplatId
)
{
return
;
}
const
wantedChatRoom
=
this
.
chatRooms
.
find
(
(
k
)
=>
k
.
uniplatId
===
data
.
uniplatId
);
if
(
wantedChatRoom
==
null
)
return
;
this
.
saveChatId
({
chatId
:
this
.
chatRooms
.
find
((
k
)
=>
k
.
uniplatId
===
data
.
uniplatId
)
.
chat_id
,
chatId
:
wantedChatRoom
.
chat_id
,
v
:
data
.
uniplat_version
,
uniplatId
:
data
.
uniplatId
,
}).
finally
(
this
.
raiseChatIdChanged
);
...
...
chat-room.vue
View file @
86932012
<
template
>
<div
class=
"chat-room-con h-100"
>
<div
class=
"room-title d-flex justify-content-between align-items-center"
>
<div
class=
"room-title d-flex justify-content-between align-items-center"
>
<div
class=
"title"
@
click=
"showMembers"
>
{{
chatTitle
}}
<template
v-if=
"chatMembers.length"
>
<span
class=
"members-count"
>
(成员
{{
chatMembers
.
length
}}
人)
</span
>
<span
class=
"members-count"
>
(成员
{{
chatMembers
.
length
}}
人)
</span>
<i
v-if=
"membersPanelVisibility"
class=
"title-right-arrow fast-service-icon-arrow-up"
/>
<i
v-else
class=
"title-right-arrow fast-service-icon-arrow-down"
/>
<i
v-else
class=
"title-right-arrow fast-service-icon-arrow-down"
/>
</
template
>
<
template
v-if=
"!notOnlyCheck"
>
<div
v-if=
"currentChat.is_finish"
class=
"chat-status chat-done"
>
<div
v-if=
"currentChat.is_finish"
class=
"chat-status chat-done"
>
已完成
</div>
<div
v-else
class=
"chat-status"
>
接待中
</div>
...
...
@@ -99,7 +89,7 @@ import messages from "@/customer-service/message-list.vue";
// import OrderInfo from "./order-info.vue"
import
{
ChatStore
,
chatStore
}
from
"@/customer-service/store/model"
;
type
RoomInfoTab
=
"customer"
|
"order"
type
RoomInfoTab
=
"customer"
|
"order"
;
@
Component
({
components
:
{
...
...
@@ -111,29 +101,29 @@ type RoomInfoTab = "customer" | "order"
})
export
default
class
ChatRoom
extends
Vue
{
@
chatStore
.
Getter
(
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
)
private
readonly
chatMembers
!
:
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
private
readonly
chatMembers
!
:
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_CLEAR_CURRENT_CHAT_MEMBERS
)
private
readonly
clearChatMembers
!
:
ChatStore
.
MUTATION_CLEAR_CURRENT_CHAT_MEMBERS
private
readonly
clearChatMembers
!
:
ChatStore
.
MUTATION_CLEAR_CURRENT_CHAT_MEMBERS
;
@
chatStore
.
State
(
ChatStore
.
STATE_CURRENT_CHAT_TITLE
)
private
readonly
chatTitle
!
:
ChatStore
.
STATE_CURRENT_CHAT_TITLE
private
readonly
chatTitle
!
:
ChatStore
.
STATE_CURRENT_CHAT_TITLE
;
@
chatStore
.
State
(
ChatStore
.
STATE_CURRENT_CHAT_INPUTING
)
private
readonly
currentInputPeople
!
:
ChatStore
.
STATE_CURRENT_CHAT_INPUTING
private
readonly
currentInputPeople
!
:
ChatStore
.
STATE_CURRENT_CHAT_INPUTING
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
)
private
readonly
currentChatUniplatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
private
readonly
currentChatUniplatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
)
private
readonly
myChatList
!
:
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
private
readonly
myChatList
!
:
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
;
private
allChatList
=
{
list
:
[]
}
private
allChatList
=
{
list
:
[]
};
@
Prop
({
type
:
Function
})
private
close
?:
()
=>
void
private
close
?:
()
=>
void
;
@
Provide
()
showReadSummary
=
true
@
Provide
()
showReadSummary
=
true
;
@
Watch
(
"currentChatUniplatId"
)
private
whenCurrentChatIdChanged
(
newValue
:
string
,
oldValue
:
string
)
{
...
...
@@ -142,8 +132,8 @@ export default class ChatRoom extends Vue {
this
.
clearChatMembers
();
}
private
activeTab
:
RoomInfoTab
=
"customer"
private
membersPanelVisibility
=
false
private
activeTab
:
RoomInfoTab
=
"customer"
;
private
membersPanelVisibility
=
false
;
private
get
getCurrentInputingPeople
()
{
return
this
.
currentInputPeople
...
...
@@ -153,9 +143,10 @@ export default class ChatRoom extends Vue {
private
get
currentChat
()
{
const
chatId
=
this
.
currentChatUniplatId
;
if
(
this
.
myChatList
==
null
)
return
;
let
result
=
this
.
myChatList
.
list
.
find
((
k
)
=>
k
.
uniplatId
===
chatId
);
if
(
result
)
return
result
;
result
=
this
.
allChatList
.
list
.
find
((
k
)
=>
k
.
uniplatId
===
chatId
);
//
if (result) return result;
//
result = this.allChatList.list.find((k) => k.uniplatId === chatId);
return
result
??
{};
}
...
...
chat.vue
View file @
86932012
...
...
@@ -30,16 +30,16 @@ import { ChatStore, chatStore } from "@/customer-service/store/model";
@
Component
({
components
:
{
MessageList
,
ChatRoom
,
ChatList
}
})
export
default
class
Chat
extends
Vue
{
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
)
private
readonly
visible
:
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
private
readonly
visible
!
:
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_HIDE_CHAT
)
private
readonly
hide
:
ChatStore
.
MUTATION_HIDE_CHAT
private
readonly
hide
!
:
ChatStore
.
MUTATION_HIDE_CHAT
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_TERINATE_CHAT
)
private
readonly
_terminate
:
ChatStore
.
ACTION_TERINATE_CHAT
private
readonly
_terminate
!
:
ChatStore
.
ACTION_TERINATE_CHAT
;
private
terminate
()
{
this
.
_terminate
();
...
...
components/message.vue
View file @
86932012
...
...
@@ -8,12 +8,7 @@
"
>
<span
class=
"no-selection"
>
<fs-avatar
:size=
"40"
class=
"msg-avatar"
:shape=
"shape"
:src=
"avatar"
/>
<fs-avatar
:size=
"40"
class=
"msg-avatar"
:shape=
"shape"
:src=
"avatar"
/>
</span>
<div
class=
"msg-content"
>
...
...
@@ -31,10 +26,7 @@
:title=
"messageBody.msg.name"
:alt=
"messageBody.msg.name"
/>
<file-icon
v-else-if=
"fileFailed2Load"
:value=
"image404"
></file-icon>
<file-icon
v-else-if=
"fileFailed2Load"
:value=
"image404"
></file-icon>
</div>
<!-- File -->
<div
...
...
@@ -115,11 +107,7 @@
<img
src=
"~@/customer-service/imgs/download.png"
alt=
"Download"
/>
</a>
<i
class=
"el-icon-warning text-danger"
v-if=
"failed"
title=
"发送失败"
></i>
<i
class=
"el-icon-warning text-danger"
v-if=
"failed"
title=
"发送失败"
></i>
<i
class=
"el-icon-loading"
v-else-if=
"isSendingMessage"
></i>
<template
v-if=
"showReadSummary"
>
...
...
@@ -170,49 +158,49 @@ import { chatStore, ChatStore } from "@/customer-service/store/model";
})
export
default
class
Message
extends
Vue
{
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_MY_ID
)
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_MY_ID
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_MY_ID
;
@
chatStore
.
Getter
(
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
)
private
readonly
chatMembers
!
:
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
private
readonly
chatMembers
!
:
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_SOURCE
)
private
readonly
chatSource
!
:
ChatStore
.
STATE_CHAT_SOURCE
private
readonly
chatSource
!
:
ChatStore
.
STATE_CHAT_SOURCE
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
/**
* tbd: 文件消息所在的域名的url,逻辑需要补充
*/
private
messageRealUrl
=
""
private
fileFailed2Load
=
false
private
image404
=
FileType
.
Image_404
private
loadingRealUrl
=
false
private
messageRealUrl
=
""
;
private
fileFailed2Load
=
false
;
private
image404
=
FileType
.
Image_404
;
private
loadingRealUrl
=
false
;
@
Prop
({
type
:
Object
,
default
:
()
=>
Object
.
create
(
null
)
})
private
data
!
:
dto
.
Message
private
data
!
:
dto
.
Message
;
@
Prop
()
private
isSendingMessage
!
:
boolean
private
isSendingMessage
!
:
boolean
;
@
Prop
()
private
failed
!
:
boolean
private
failed
!
:
boolean
;
@
Prop
({
default
:
"circle"
})
private
shape
!
:
string
private
shape
!
:
string
;
@
Ref
(
"audio"
)
private
readonly
audioRef
!
:
HTMLAudioElement
private
readonly
audioRef
!
:
HTMLAudioElement
;
private
playing
=
false
private
playing
=
false
;
@
Inject
({
default
:
false
})
readonly
showReadSummary
!
:
boolean
@
Inject
({
default
:
false
})
readonly
showReadSummary
!
:
boolean
;
private
emptyText
=
" "
private
emptyText
=
" "
;
private
readListVisibility
=
false
private
readListVisibility
=
false
;
private
org
=
""
private
org
=
""
;
private
get
isAllRead
()
{
return
this
.
data
.
read_count
>=
this
.
data
.
total_read_count
;
...
...
@@ -251,8 +239,7 @@ export default class Message extends Vue {
const
source
=
msg
.
msg
.
source
;
if
(
source
)
{
return
(
source
===
this
.
chatSource
&&
this
.
messageBody
.
eid
===
this
.
chatMyId
source
===
this
.
chatSource
&&
this
.
messageBody
.
eid
===
this
.
chatMyId
);
}
...
...
@@ -273,7 +260,7 @@ export default class Message extends Vue {
}
private
get
username
()
{
const
avatar
=
chat
.
getUserMapping
();
const
avatar
:
any
=
chat
.
getUserMapping
();
if
(
this
.
data
==
null
)
return
""
;
if
(
avatar
==
null
)
return
this
.
data
.
id
;
const
value
=
avatar
[
this
.
data
.
eid
];
...
...
components/who-read-list.vue
View file @
86932012
...
...
@@ -9,31 +9,15 @@
<template
v-if=
"!loading"
>
<div
class=
"list-left"
>
<div
class=
"number-count"
>
已读
{{
readlist
.
length
}}
</div>
<div
class=
"member-item"
v-for=
"item in readlist"
:key=
"item.eid"
>
<fs-avatar
class=
"member-avatar"
:src=
"item.avatar"
:size=
"30"
/>
<div
class=
"member-item"
v-for=
"item in readlist"
:key=
"item.eid"
>
<fs-avatar
class=
"member-avatar"
:src=
"item.avatar"
:size=
"30"
/>
<span
class=
"member-name"
>
{{
item
.
name
}}
</span>
</div>
</div>
<div
class=
"list-right"
>
<div
class=
"number-count"
>
未读
{{
unreadlist
.
length
}}
</div>
<div
class=
"member-item"
v-for=
"item in unreadlist"
:key=
"item.eid"
>
<fs-avatar
class=
"member-avatar"
:src=
"item.avatar"
:size=
"30"
/>
<div
class=
"member-item"
v-for=
"item in unreadlist"
:key=
"item.eid"
>
<fs-avatar
class=
"member-avatar"
:src=
"item.avatar"
:size=
"30"
/>
<span
class=
"member-name"
>
{{
item
.
name
}}
</span>
</div>
</div>
...
...
@@ -49,29 +33,30 @@ import { unique } from "../utils";
import
{
ChatStore
}
from
"@/customer-service/store/model"
;
import
xim
from
"@/customer-service/xim/xim"
;
import
chat
from
"@/customer-service/xim/index"
;
const
chatStoreNamespace
=
namespace
(
"chatStore"
);
@
Component
({
components
:
{}
})
export
default
class
WhoReadList
extends
Vue
{
@
chatStoreNamespace
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStoreNamespace
.
State
(
ChatStore
.
STATE_CHAT_MY_ID
)
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_MY_ID
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_MY_ID
;
@
Prop
({
type
:
Number
,
})
private
msgId
!
:
number
private
msgId
!
:
number
;
@
Ref
(
"list-con"
)
private
listCon
!
:
HTMLElement
private
listCon
!
:
HTMLElement
;
private
top
=
0
private
left
=
0
private
readlist
:
{
name
:
string
;
avatar
:
string
}[]
=
[]
private
unreadlist
:
{
name
:
string
;
avatar
:
string
}[]
=
[]
private
loading
=
false
private
top
=
0
;
private
left
=
0
;
private
readlist
:
{
name
:
string
;
avatar
:
string
}[]
=
[];
private
unreadlist
:
{
name
:
string
;
avatar
:
string
}[]
=
[];
private
loading
=
false
;
private
startLoading
()
{
this
.
loading
=
true
;
}
...
...
@@ -88,9 +73,8 @@ export default class WhoReadList extends Vue {
public
mounted
()
{
this
.
enableBlur
();
const
{
top
,
left
}
=
(
this
.
listCon
.
parentNode
as
HTMLElement
).
getBoundingClientRect
();
const
{
top
,
left
}
=
(
this
.
listCon
.
parentNode
as
HTMLElement
).
getBoundingClientRect
();
this
.
top
=
top
;
this
.
left
=
left
;
}
...
...
@@ -101,7 +85,11 @@ export default class WhoReadList extends Vue {
}
private
async
getUserNameByid
(
eid
:
string
)
{
const
data
=
await
this
.
sdk
.
model
(
"user"
).
detail
(
eid
).
query
();
const
data
=
await
chat
.
getSdk
()
.
model
(
"user"
)
.
detail
(
eid
)
.
query
();
return
data
.
row
.
first_name
.
value
as
string
;
}
...
...
@@ -109,6 +97,7 @@ export default class WhoReadList extends Vue {
if
(
this
.
chatId
==
null
)
return
;
if
(
this
.
msgId
==
null
)
return
;
const
data
=
await
xim
.
fetchMsgInBox
(
this
.
chatId
,
this
.
msgId
);
if
(
data
==
null
)
return
;
const
readerlist
=
this
.
uniqueReaderList
(
data
.
args
[
0
]
as
dto
.
OneWhoReadMessage
[]
);
...
...
@@ -143,7 +132,7 @@ export default class WhoReadList extends Vue {
}
private
uniqueReaderList
(
data
:
dto
.
OneWhoReadMessage
[])
{
return
unique
(
data
,
function
(
item
,
all
)
{
return
unique
(
data
,
function
(
item
,
all
)
{
return
all
.
findIndex
((
k
)
=>
k
.
eid
===
item
.
eid
);
});
}
...
...
create-chat.vue
View file @
86932012
...
...
@@ -10,10 +10,8 @@
@
selection-change=
"handleSelectionChange"
>
<el-table-column
type=
"selection"
width=
"55"
>
</el-table-column>
<el-table-column
prop=
"id"
label=
"id"
width=
"150"
>
</el-table-column>
<el-table-column
prop=
"name"
label=
"name"
width=
"150"
>
</el-table-column>
<el-table-column
prop=
"id"
label=
"id"
width=
"150"
>
</el-table-column>
<el-table-column
prop=
"name"
label=
"name"
width=
"150"
>
</el-table-column>
</el-table>
<el-button
@
click=
"nextPage"
>
加载下一页
</el-button>
<span
slot=
"footer"
class=
"dialog-footer"
>
...
...
@@ -23,66 +21,70 @@
</el-dialog>
</
template
>
<
script
lang=
"ts"
>
import
{
Component
,
Vue
}
from
"vue-property-decorator"
import
{
ChatStore
,
chatStore
}
from
"@/customer-service/store/model"
import
{
List
,
ListEasy
,
ListTypes
}
from
"uniplat-sdk"
import
{
Component
,
Vue
}
from
"vue-property-decorator"
;
import
{
ChatStore
,
chatStore
}
from
"@/customer-service/store/model"
;
import
type
{
List
,
ListEasy
,
ListTypes
}
from
"uniplat-sdk"
;
import
chat
from
"@/customer-service/xim/index"
type
User
=
{
id
:
string
name
:
string
}
type
ThenArg
<
T
>
=
T
extends
PromiseLike
<
infer
U
>
?
U
:
T
id
:
string
;
name
:
string
;
}
;
type
ThenArg
<
T
>
=
T
extends
PromiseLike
<
infer
U
>
?
U
:
T
;
@
Component
({
components
:
{}
})
export
default
class
ChatCreator
extends
Vue
{
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CREATOR_VISIBLE
)
private
readonly
visible
:
ChatStore
.
STATE_CHAT_CREATOR_VISIBLE
private
readonly
visible
!
:
ChatStore
.
STATE_CHAT_CREATOR_VISIBLE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_HIDE_CHAT_CREATOR
)
private
readonly
hide
:
ChatStore
.
MUTATION_HIDE_CHAT_CREATOR
private
readonly
hide
!
:
ChatStore
.
MUTATION_HIDE_CHAT_CREATOR
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
)
private
readonly
_createChat
:
ChatStore
.
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
private
readonly
_createChat
!
:
ChatStore
.
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
;
private
currentPage
=
1
private
userList
=
[]
private
getList
:
ThenArg
<
ReturnType
<
ListEasy
[
"query"
]
>>
[
"getList"
]
private
currentPage
=
1
;
private
userList
:
{
id
:
any
;
name
:
any
;
}[]
=
[];
private
getList
!
:
ThenArg
<
ReturnType
<
ListEasy
[
"query"
]
>>
[
"getList"
];
public
async
created
()
{
const
list
=
this
.
sdk
.
model
(
"user"
).
list
()
const
list
=
chat
.
getSdk
().
model
(
"user"
).
list
();
const
{
pageData
,
getList
}
=
await
list
.
query
({
pageIndex
:
this
.
currentPage
,
item_size
:
50
,
})
this
.
getList
=
getList
this
.
userList
=
this
.
exactUserList
(
pageData
.
rows
)
});
this
.
getList
=
getList
;
this
.
userList
=
this
.
exactUserList
(
pageData
.
rows
);
}
private
exactUserList
(
rows
:
any
[])
{
return
rows
.
map
((
k
)
=>
{
return
{
id
:
k
.
id
.
value
,
name
:
k
.
first_name
.
value
,
}
})
};
});
}
private
loading
=
false
private
loading
=
false
;
private
async
nextPage
()
{
this
.
loading
=
true
this
.
currentPage
++
const
data
=
await
this
.
getList
(
this
.
currentPage
)
this
.
loading
=
false
this
.
userList
=
[...
this
.
userList
,
...
this
.
exactUserList
(
data
.
rows
)]
this
.
loading
=
true
;
this
.
currentPage
++
;
const
data
=
await
this
.
getList
(
this
.
currentPage
);
this
.
loading
=
false
;
this
.
userList
=
[...
this
.
userList
,
...
this
.
exactUserList
(
data
.
rows
)];
}
private
selectedRows
:
string
[]
=
[]
private
selectedRows
:
string
[]
=
[];
private
handleSelectionChange
(
selectedRows
:
User
[])
{
this
.
selectedRows
=
selectedRows
.
map
((
k
)
=>
String
(
k
.
id
))
this
.
selectedRows
=
selectedRows
.
map
((
k
)
=>
String
(
k
.
id
));
}
private
createChat
()
{
const
{
keyvalue
,
model_name
}
=
this
.
$route
.
params
const
{
keyvalue
,
model_name
}
=
this
.
$route
.
params
;
this
.
_createChat
({
modelName
:
model_name
,
selectedListId
:
keyvalue
,
uids
:
this
.
selectedRows
,
})
});
}
}
</
script
>
...
...
hybrid-input/index.vue
View file @
86932012
...
...
@@ -13,20 +13,10 @@
@
click=
"allowLoadImg"
class=
"offset"
>
<img
class=
"tool-bar-icon"
src=
"@/customer-service/imgs/pic.png"
/>
<img
class=
"tool-bar-icon"
src=
"@/customer-service/imgs/pic.png"
/>
</label>
<label
for=
"chat-upload-file"
:title=
"tip4File"
@
click=
"allowLoadFile"
>
<img
class=
"tool-bar-icon"
src=
"@/customer-service/imgs/file.png"
/>
<label
for=
"chat-upload-file"
:title=
"tip4File"
@
click=
"allowLoadFile"
>
<img
class=
"tool-bar-icon"
src=
"@/customer-service/imgs/file.png"
/>
</label>
<input
...
...
@@ -123,19 +113,19 @@ export function isImageOrFile(node: ChildNode) {
@
Component
({
components
:
{}
})
export
default
class
Input
extends
Vue
{
@
chatStoreNamespace
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
Ref
(
"input"
)
private
readonly
messageInputBox
!
:
HTMLDivElement
private
readonly
messageInputBox
!
:
HTMLDivElement
;
private
file
=
""
private
acceptType
=
"image/*"
private
emojiPanelVisibility
=
false
private
file
=
""
;
private
acceptType
=
"image/*"
;
private
emojiPanelVisibility
=
false
;
private
tip4Image
=
`发送图片(最大
${
MAX_IMAGE_SIZE_STRING
}
)`
private
tip4File
=
`发送文件(最大
${
MAX_FILE_SIZE_STRING
}
)`
private
tip4Image
=
`发送图片(最大
${
MAX_IMAGE_SIZE_STRING
}
)`
;
private
tip4File
=
`发送文件(最大
${
MAX_FILE_SIZE_STRING
}
)`
;
private
emoji
:
{
name
:
string
;
emoji_chars
:
string
;
code
:
string
}[]
=
[]
private
emoji
:
{
name
:
string
;
emoji_chars
:
string
;
code
:
string
}[]
=
[];
@
Watch
(
"chatId"
)
private
onChatIdChanged
(
v
:
number
,
old
:
number
)
{
...
...
@@ -151,9 +141,7 @@ export default class Input extends Vue {
if
(
v
)
{
const
cache
=
chatCache
[
v
];
if
(
cache
)
{
const
e
=
document
.
querySelector
(
"#chat-input-box"
)
as
HTMLElement
;
const
e
=
document
.
querySelector
(
"#chat-input-box"
)
as
HTMLElement
;
if
(
e
)
{
for
(
const
node
of
cache
as
ChildNode
[])
{
e
.
appendChild
(
node
);
...
...
@@ -167,17 +155,15 @@ export default class Input extends Vue {
this
.
messageInputBox
.
addEventListener
(
"paste"
,
this
.
handlePasteEvent
);
document
.
addEventListener
(
"click"
,
this
.
hideEmoji
);
document
.
addEventListener
(
"selectionchange"
,
this
.
handleSaveRange
);
this
.
$onBeforeDestroy
(()
=>
{
document
.
removeEventListener
(
"click"
,
this
.
hideEmoji
);
document
.
removeEventListener
(
"selectionchange"
,
this
.
handleSaveRange
);
});
this
.
setupEmoji
();
this
.
focus
();
}
public
beforeDestroy
()
{
document
.
removeEventListener
(
"click"
,
this
.
hideEmoji
);
document
.
removeEventListener
(
"selectionchange"
,
this
.
handleSaveRange
);
}
public
focus
()
{
this
.
messageInputBox
.
focus
();
}
...
...
@@ -250,8 +236,7 @@ export default class Input extends Vue {
html
+=
k
;
}
}
else
if
(
i
===
1
)
{
const
srcRegex
=
/<img
[^
>
]
+src="
([^
">
]
+
)
"
\s
+title="
([^
">
]
+
)
"/g
;
const
srcRegex
=
/<img
[^
>
]
+src="
([^
">
]
+
)
"
\s
+title="
([^
">
]
+
)
"/g
;
let
result
;
do
{
result
=
srcRegex
.
exec
(
k
);
...
...
@@ -373,15 +358,15 @@ export default class Input extends Vue {
}
}
private
saveRange
?:
(
param
:
Range
)
=>
void
private
getRange
?:
()
=>
Range
|
null
private
saveRange
?:
(
param
:
Range
)
=>
void
;
private
getRange
?:
()
=>
Range
|
null
;
/**
* 在光标处插入元素
*/
public
insertHtmlAtCaret
=
(
function
()
{
public
insertHtmlAtCaret
=
(
function
()
{
let
range
:
Range
|
null
=
null
;
return
function
(
this
:
Input
,
html
:
string
)
{
return
function
(
this
:
Input
,
html
:
string
)
{
if
(
this
.
saveRange
==
null
)
{
this
.
saveRange
=
(
alternate
)
=>
(
range
=
alternate
);
}
...
...
@@ -423,7 +408,7 @@ export default class Input extends Vue {
}
}
};
})()
})();
private
selectEmoji
(
emoji
:
any
)
{
this
.
insertHtmlAtCaret
(
emoji
.
emoji_chars
);
...
...
message-input.vue
View file @
86932012
...
...
@@ -27,34 +27,34 @@ let sendingMessageIndex = 1;
@
Component
({
components
:
{
ChatInput
}
})
export
default
class
MessageInput
extends
Vue
{
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
)
private
readonly
chatRoomVisible
:
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
private
readonly
chatRoomVisible
!
:
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_SEND_MESSAGE
)
private
readonly
sendMsg
!
:
ChatStore
.
ACTION_SEND_MESSAGE
private
readonly
sendMsg
!
:
ChatStore
.
ACTION_SEND_MESSAGE
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_MY_ID
)
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_MY_ID
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_MY_ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_CURRENT_CHAT_INITING
)
private
readonly
chatIniting
!
:
ChatStore
.
STATE_CURRENT_CHAT_INITING
private
readonly
chatIniting
!
:
ChatStore
.
STATE_CURRENT_CHAT_INITING
;
@
chatStore
.
Getter
(
ChatStore
.
STATE_CHAT_SOURCE
)
private
readonly
source
!
:
ChatStore
.
STATE_CHAT_SOURCE
private
readonly
source
!
:
ChatStore
.
STATE_CHAT_SOURCE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_APPEND_SENDING_MESSAGE
)
private
readonly
appendSendingMessages
!
:
ChatStore
.
MUTATION_APPEND_SENDING_MESSAGE
private
readonly
appendSendingMessages
!
:
ChatStore
.
MUTATION_APPEND_SENDING_MESSAGE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_FAILED_SENDING_MESSAGE
)
private
readonly
failedSendingMessage
!
:
ChatStore
.
MUTATION_FAILED_SENDING_MESSAGE
private
readonly
failedSendingMessage
!
:
ChatStore
.
MUTATION_FAILED_SENDING_MESSAGE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_REMOVE_SENDING_MESSAGE
)
private
readonly
removeSendingMessages
!
:
ChatStore
.
MUTATION_REMOVE_SENDING_MESSAGE
private
readonly
removeSendingMessages
!
:
ChatStore
.
MUTATION_REMOVE_SENDING_MESSAGE
;
@
Ref
(
"chat-input"
)
private
readonly
chatInput
!
:
ChatInput
private
readonly
chatInput
!
:
ChatInput
;
@
Watch
(
"chatRoomVisible"
)
private
whenChatRoomShow
()
{
...
...
@@ -101,9 +101,7 @@ export default class MessageInput extends Vue {
}
private
async
sendFile
(
file
:
any
,
type
:
"image"
|
"file"
)
{
const
src
=
JSON
.
parse
(
file
.
attributes
[
`data-
${
type
}
`
]?.
value
||
""
)
as
{
const
src
=
JSON
.
parse
(
file
.
attributes
[
`data-
${
type
}
`
]?.
value
||
""
)
as
{
url
:
string
;
name
:
string
;
size
:
number
;
...
...
@@ -117,7 +115,7 @@ export default class MessageInput extends Vue {
if
(
type
===
"image"
)
{
const
img
=
new
Image
();
img
.
src
=
src
.
url
;
img
.
onload
=
function
()
{
img
.
onload
=
function
()
{
w
=
img
.
naturalWidth
;
h
=
img
.
naturalHeight
;
};
...
...
service/request.ts
View file @
86932012
import
Axios
from
"axios"
;
import
qs
from
"qs"
;
import
chat
from
"../xim/index"
;
export
function
buildConfig
(
token
:
string
,
url
:
string
)
{
if
(
url
&&
url
.
includes
(
"/general"
))
{
return
{
headers
:
{
Authorization
:
token
,
CurrentOrg
:
this
.
station
}
};
return
{
headers
:
{
Authorization
:
token
,
CurrentOrg
:
chat
.
getOrgId
()
}
};
}
return
{
headers
:
{
Authorization
:
token
}
};
}
...
...
store/index.ts
View file @
86932012
...
...
@@ -29,7 +29,7 @@ function uniqueMessages(
messages
:
NonNullable
<
ChatStore
.
STATE_CHAT_MSG_HISTORY
>
)
{
const
arr
=
[...
messages
];
return
unique
(
arr
,
function
(
item
,
all
)
{
return
unique
(
arr
,
function
(
item
,
all
)
{
return
all
.
findIndex
((
k
)
=>
k
.
id
===
item
.
id
);
});
}
...
...
@@ -254,7 +254,7 @@ export default {
state
[
ChatStore
.
STATE_CHAT_SENDING_MESSAGES
]
=
[...
current
];
}
},
[
ChatStore
.
MUTATION_SAVE_CURRENT_CHAT_INPUTING
]:
(
function
()
{
[
ChatStore
.
MUTATION_SAVE_CURRENT_CHAT_INPUTING
]:
(
function
()
{
const
setTimeoutId
:
{
[
key
:
string
]:
number
}
=
{};
return
(
state
:
ChatStoreState
,
...
...
@@ -441,9 +441,12 @@ export default {
.
dryExecute
();
commit
(
ChatStore
.
MUTATION_HIDE_CHAT_CREATOR
);
await
dispatch
(
ChatStore
.
ACTION_GET_MY_CHAT_LIST
);
const
newChat
=
state
[
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
].
list
.
find
(
const
roomList
=
state
[
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
];
if
(
roomList
==
null
)
return
const
newChat
=
roomList
.
list
.
find
(
(
k
)
=>
k
.
uniplatId
===
id
);
if
(
newChat
==
null
)
return
commit
(
ChatStore
.
MUTATION_SHOW_CHAT
);
await
dispatch
(
ChatStore
.
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
,
{
chatId
:
newChat
.
chat_id
,
...
...
@@ -538,10 +541,11 @@ export default {
const
chatId
=
state
[
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
];
if
(
chatId
==
null
)
return
;
const
getChatMembersResult
=
await
xim
.
fetchChatMembers
(
chatId
);
if
(
getChatMembersResult
==
null
)
return
const
chatMembers
=
getChatMembersResult
.
args
[
0
]
as
ChatMember
[];
const
newChatMembers
=
await
Promise
.
all
(
chatMembers
.
map
(
async
(
member
)
=>
{
let
result
:
ChatStore
.
STATE_CURRENT_CHAT_MEMBERS
[
number
];
let
result
:
NonNullable
<
ChatStore
.
STATE_CURRENT_CHAT_MEMBERS
>
[
number
];
try
{
const
info
=
await
sdk
()
.
model
(
"user"
)
...
...
@@ -562,13 +566,14 @@ export default {
);
commit
(
ChatStore
.
MUTATION_SAVE_CURRENT_CHAT_MEMBERS
,
unique
(
newChatMembers
,
function
(
item
,
all
)
{
unique
(
newChatMembers
,
function
(
item
,
all
)
{
return
all
.
findIndex
((
k
)
=>
k
.
eid
===
item
.
eid
);
})
);
},
async
[
ChatStore
.
ACTION_TERINATE_CHAT
]({
state
,
dispatch
})
{
const
v
=
state
[
ChatStore
.
STATE_CHAT_CURRENT_CHAT_VERSION
];
if
(
v
==
null
)
return
const
id
=
Number
(
state
[
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
]
);
...
...
store/model.ts
View file @
86932012
...
...
@@ -30,7 +30,7 @@ export namespace ChatStore {
is_finish
:
0
|
1
;
}[];
total
:
number
;
}
}
|
null
export
const
STATE_CHAT_MSG_HISTORY
=
"某个会话聊天记录"
;
export
type
STATE_CHAT_MSG_HISTORY
=
dto
.
MessageRequestResult
|
null
...
...
xim/index.ts
View file @
86932012
...
...
@@ -44,7 +44,9 @@ class Chat {
}
public
getSdk
=
()
=>
{
if
(
this
.
_sdk
==
null
)
return
;
if
(
this
.
_sdk
==
null
)
{
throw
new
Error
(
"sdk shouldn't undefined"
)
};
return
this
.
_sdk
();
};
...
...
@@ -90,7 +92,7 @@ class Chat {
}
public
getUserMapping
()
{
return
{};
return
{}
as
any
;
}
private
debug
(
message
:
string
)
{
...
...
xim/xim.ts
View file @
86932012
...
...
@@ -145,7 +145,9 @@ export class Xim {
desc
=
true
):
Promise
<
Message
[]
>
{
this
.
checkConnected
();
if
(
this
.
client
==
null
)
return
;
if
(
this
.
client
==
null
)
{
throw
new
Error
(
"client shouldn't undefined"
)
};
const
res
=
await
this
.
client
.
fetchChatMsgs
(
chatType
,
chatId
,
{
lid
,
rid
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment