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
452addc8
authored
Sep 08, 2021
by
Sixong.Zhu
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
eslint
parent
a5b8ff29
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
1197 additions
and
1039 deletions
components/chat-container.vue
components/chat-list-model.vue
components/chat-list.vue
components/chat-room.vue
components/chat-title.vue
components/controller/chat-list.ts
components/controller/index.ts
components/message-input.vue
hybrid-input/index.vue
store/model.ts
xim/index.ts
components/chat-container.vue
View file @
452addc8
<
template
>
<div
class=
"chat-container"
:class=
"
{'is-in-page': isInPage}">
<div
class=
"search-wrap"
v-if=
"!modelName"
>
<el-input
class=
"keyword-input"
placeholder=
"会话标题"
prefix-icon=
"el-icon-search"
v-model=
"searchKeyword"
v-on:keyup
.
enter
.
native=
"search"
clearable
@
clear=
"search"
></el-input>
<i
v-if=
"!isInPage"
class=
"close-btn el-icon-close"
@
click=
"$emit('close')"
></i>
<div
class=
"chat-container"
:class=
"
{ 'is-in-page': isInPage }">
<div
class=
"search-wrap"
v-if=
"!modelName"
>
<el-input
class=
"keyword-input"
placeholder=
"会话标题"
prefix-icon=
"el-icon-search"
v-model=
"searchKeyword"
v-on:keyup
.
enter
.
native=
"search"
clearable
@
clear=
"search"
></el-input>
<i
v-if=
"!isInPage"
class=
"close-btn el-icon-close"
@
click=
"$emit('close')"
></i>
</div>
<chat-list
v-if=
"!modelName"
ref=
"chatListComp"
@
list-count-update=
"$emit('list-count-update', $event)"
/>
<chat-list-model
v-if=
"modelName"
@
list-count-update=
"$emit('list-count-update', $event)"
ref=
"chatListModel"
:modelName=
"modelName"
:listName=
"listName"
/>
<div
class=
"chat-content-wrap"
v-if=
"chatVisible && onShow"
>
<chat
:modelName=
"modelName"
@
updateActive=
"$emit('updateActive', $event)"
/>
</div>
</div>
<chat-list
v-if=
"!modelName"
ref=
"chatListComp"
@
list-count-update=
"$emit('list-count-update', $event)"
/>
<chat-list-model
v-if=
"modelName"
@
list-count-update=
"$emit('list-count-update', $event)"
ref=
"chatListModel"
:modelName=
"modelName"
:listName=
"listName"
/>
<div
class=
"chat-content-wrap"
v-if=
"chatVisible && onShow"
>
<chat
:modelName=
"modelName"
@
updateActive=
"$emit('updateActive', $event)"
/>
</div>
</div>
</
template
>
<
script
lang=
"ts"
>
...
...
@@ -29,91 +45,94 @@ import chat from "@/customer-service/chat.vue";
import
{
ChatStore
,
chatStore
}
from
"@/customer-service/store/model"
;
@
Component
({
name
:
"ChatContainer"
,
components
:
{
chat
,
ChatList
,
ChatListModel
}
name
:
"ChatContainer"
,
components
:
{
chat
,
ChatList
,
ChatListModel
,
},
})
export
default
class
ChatContainer
extends
Vue
{
@
Prop
(
Boolean
)
isInPage
:
boolean
;
@
Prop
(
String
)
modelName
:
string
;
@
Prop
(
String
)
listName
:
string
;
@
Prop
(
String
)
activeName
:
string
;
@
Prop
(
Boolean
)
isActive
:
boolean
;
export
default
class
ChatContainer
extends
Vue
{
@
Prop
(
Boolean
)
isInPage
:
boolean
;
@
Prop
(
String
)
modelName
:
string
;
@
Prop
(
String
)
listName
:
string
;
@
Prop
(
String
)
activeName
:
string
;
@
Prop
(
Boolean
)
isActive
:
boolean
;
private
onShow
=
false
;
private
onShow
=
false
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
)
private
readonly
chatVisible
!
:
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
)
private
readonly
chatVisible
!
:
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_HIDE_CHAT
)
private
readonly
hideChat
:
ChatStore
.
MUTATION_HIDE_CHAT
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_HIDE_CHAT
)
private
readonly
hideChat
:
ChatStore
.
MUTATION_HIDE_CHAT
;
@
Ref
(
"chatListComp"
)
chatListComp
:
ChatList
;
private
searchKeyword
=
""
;
@
Ref
(
"chatListComp"
)
chatListComp
:
ChatList
;
private
searchKeyword
=
""
;
@
Ref
(
"chatListModel"
)
chatListModel
:
ChatListModel
;
@
Ref
(
"chatListModel"
)
chatListModel
:
ChatListModel
;
private
search
()
{
this
.
chatListComp
.
search
(
this
.
searchKeyword
)
}
private
search
()
{
this
.
chatListComp
.
search
(
this
.
searchKeyword
);
}
@
Watch
(
"isActive"
,
{
immediate
:
true
})
isActiveUpdate
()
{
this
.
onShow
=
this
.
isActive
;
if
(
!
this
.
onShow
&&
this
.
activeName
!==
"my_receiving"
||
this
.
onShow
&&
this
.
listName
)
{
this
.
chatListModel
&&
this
.
chatListModel
.
clearActiveId
();
@
Watch
(
"isActive"
,
{
immediate
:
true
})
isActiveUpdate
()
{
this
.
onShow
=
this
.
isActive
;
if
(
(
!
this
.
onShow
&&
this
.
activeName
!==
"my_receiving"
)
||
(
this
.
onShow
&&
this
.
listName
)
)
{
this
.
chatListModel
&&
this
.
chatListModel
.
clearActiveId
();
}
}
}
private
onChatDrawerClose
()
{
this
.
hideChat
()
}
private
onChatDrawerClose
()
{
this
.
hideChat
();
}
}
</
script
>
<
style
lang=
"less"
>
.chat-container
{
.chat-container
{
height
:
70vh
;
&.is-in-page
{
height
:
100%
;
height
:
100%
;
}
}
.keyword-input
{
}
.keyword-input
{
width
:
300px
;
margin
:
15px
0
14px
20px
;
/deep/
.el-input__inner
{
font-size
:
13px
;
height
:
30px
;
line-height
:
28px
;
border-radius
:
15px
;
padding-right
:
15px
;
font-size
:
13px
;
height
:
30px
;
line-height
:
28px
;
border-radius
:
15px
;
padding-right
:
15px
;
}
/
deep
/
.el-icon-time
{
background
:
transparent
;
background
:
transparent
;
}
/
deep
/
.el-input__icon
{
line-height
:
32px
;
line-height
:
32px
;
}
}
.search-wrap
{
}
.search-wrap
{
height
:
59px
;
border-bottom
:
1px
solid
#ddd
;
}
.close-btn
{
}
.close-btn
{
float
:
right
;
font-size
:
20px
;
color
:
#aaa
;
padding
:
5px
;
cursor
:
pointer
;
margin
:
15px
10px
0
;
}
.chat-content-wrap
{
}
.chat-content-wrap
{
display
:
inline-block
;
width
:
75%
;
height
:
calc
(
100%
-
60px
);
box-sizing
:
border-box
;
vertical-align
:
top
;
}
</
style
>
\ No newline at end of file
}
</
style
>
components/chat-list-model.vue
View file @
452addc8
...
...
@@ -2,7 +2,10 @@
<div
class=
"chat-list-con"
>
<div
class=
"chat-list h-100"
>
<div
class=
"chat-list-scroll"
>
<el-scrollbar
ref=
"scrollbar"
class=
"list-scroll no-bottom-scrollbar"
>
<el-scrollbar
ref=
"scrollbar"
class=
"list-scroll no-bottom-scrollbar"
>
<div
v-for=
"item in chatRooms"
:key=
"'room_' + item.id"
...
...
@@ -11,8 +14,14 @@
@click="goToChatRoom(item)"
>
<div
class=
"chat-info"
>
<div
class=
"checkbox-wrap"
@
click
.
stop
v-if=
"showItemCheckbox"
>
<el-checkbox
v-model=
"item.checked"
></el-checkbox>
<div
class=
"checkbox-wrap"
@
click
.
stop
v-if=
"showItemCheckbox"
>
<el-checkbox
v-model=
"item.checked"
></el-checkbox>
</div>
<div
class=
"w-100"
>
<div
...
...
@@ -24,16 +33,28 @@
"
>
<div
class=
"chat-name flex-fill text-dot-dot-dot"
class=
"
chat-name
flex-fill
text-dot-dot-dot
"
>
<span>
{{
item
.
title
}}
</span>
</div>
<div
v-if=
"item.last_msg_ts"
class=
"chat-time"
>
<div
v-if=
"item.last_msg_ts"
class=
"chat-time"
>
{{
formatTimestamp
(
item
.
last_msg_ts
)
}}
</div>
</div>
<div
class=
"chat-msg text-dot-dot-dot"
>
{{
userNames
[
item
.
last_msg_sender
]
?
userNames
[
item
.
last_msg_sender
]
+
": "
:
""
}}{{
parseMesage
(
item
)
}}
{{
userNames
[
item
.
last_msg_sender
]
?
userNames
[
item
.
last_msg_sender
]
+
": "
:
""
}}{{
parseMesage
(
item
)
}}
</div>
</div>
</div>
...
...
@@ -60,9 +81,21 @@
></el-pagination>
<div
class=
"action-row"
>
<el-button
@
click=
"getList"
>
刷新
</el-button>
<el-button
v-if=
"!showItemCheckbox"
@
click=
"showItemCheckbox = true"
>
批量接待
</el-button>
<el-button
v-if=
"showItemCheckbox"
@
click=
"batchStartReception"
>
确定接待
</el-button>
<el-button
v-if=
"showItemCheckbox"
@
click=
"showItemCheckbox = false"
>
取消
</el-button>
<el-button
v-if=
"!showItemCheckbox"
@
click=
"showItemCheckbox = true"
>
批量接待
</el-button
>
<el-button
v-if=
"showItemCheckbox"
@
click=
"batchStartReception"
>
确定接待
</el-button
>
<el-button
v-if=
"showItemCheckbox"
@
click=
"showItemCheckbox = false"
>
取消
</el-button
>
</div>
</div>
</div>
...
...
@@ -74,22 +107,8 @@ import { Component, Prop, Ref, Vue } from "vue-property-decorator";
import
{
chatStore
,
ChatStore
}
from
"@/customer-service/store/model"
;
import
{
formatTime
,
TimeFormatRule
}
from
"@/customer-service/utils/time"
;
import
{
Chat
as
ChatType
}
from
"@/customer-service/xim/models/chat"
;
import
{
EVENTS
}
from
"@/EventConsts"
export
function
parserMessage
(
type
:
string
,
rawMsg
:
string
)
{
if
(
!
type
)
return
"[空]"
;
if
(
!
rawMsg
)
return
"[空]"
;
const
msg
=
JSON
.
parse
(
rawMsg
);
if
(
type
===
"text"
)
{
return
msg
.
text
;
}
else
if
(
type
===
"image"
)
{
return
`[图片]`
;
}
else
if
(
type
===
"file"
)
{
return
`[文件]`
;
}
else
{
return
`[系统自动回复]`
;
}
}
import
{
EVENTS
}
from
"@/EventConsts"
;
import
{
parserMessage
}
from
"./controller"
;
interface
SelectChatType
extends
ChatType
{
checked
?:
boolean
;
...
...
@@ -97,7 +116,6 @@ interface SelectChatType extends ChatType {
@
Component
({
components
:
{}
})
export
default
class
ModelChatList
extends
Vue
{
@
chatStore
.
Action
(
ChatStore
.
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
)
private
readonly
_createChat
!
:
ChatStore
.
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
;
...
...
@@ -146,20 +164,26 @@ export default class ModelChatList extends Vue {
}
private
async
getList
()
{
let
result
=
await
this
.
sdk
.
model
(
this
.
modelName
).
list
(
this
.
listName
||
undefined
).
query
({
pageIndex
:
this
.
currentPage
,
item_size
:
this
.
pageSize
})
let
result
=
await
this
.
sdk
.
model
(
this
.
modelName
)
.
list
(
this
.
listName
||
undefined
)
.
query
({
pageIndex
:
this
.
currentPage
,
item_size
:
this
.
pageSize
,
});
if
(
result
.
pageData
.
rows
.
length
===
0
&&
this
.
currentPage
!==
1
)
{
this
.
currentPage
=
1
;
if
(
result
.
pageData
.
record_count
>
0
)
{
result
=
await
this
.
sdk
.
model
(
this
.
modelName
).
list
(
this
.
listName
||
undefined
).
query
({
pageIndex
:
this
.
currentPage
,
item_size
:
this
.
pageSize
})
result
=
await
this
.
sdk
.
model
(
this
.
modelName
)
.
list
(
this
.
listName
||
undefined
)
.
query
({
pageIndex
:
this
.
currentPage
,
item_size
:
this
.
pageSize
,
});
}
}
this
.
chatList
=
result
.
pageData
.
rows
.
map
(
it
=>
{
this
.
chatList
=
result
.
pageData
.
rows
.
map
(
(
it
)
=>
{
return
{
id
:
it
.
id
.
value
,
chat_id
:
it
.
ImChatId
.
value
,
...
...
@@ -172,9 +196,9 @@ export default class ModelChatList extends Vue {
last_msg_content
:
it
.
LastMsgContent
.
value
,
last_msg_ts
:
it
.
LastMsgTime
.
value
,
last_msg_type
:
it
.
LastMsgType
.
value
,
title
:
it
.
Title
.
value
}
as
ChatType
})
title
:
it
.
Title
.
value
,
}
as
ChatType
;
})
;
this
.
total
=
result
.
pageData
.
record_count
;
this
.
$emit
(
"list-count-update"
,
this
.
total
);
}
...
...
@@ -183,24 +207,40 @@ export default class ModelChatList extends Vue {
await
this
.
getList
();
this
.
setSource
(
ChatStore
.
StateChatSourceDirection
.
Server
);
this
.
scrollbar
.
update
();
await
this
.
sdk
.
model
(
"UniplatChat"
).
registerOnChange
(
this
.
onTransportMessage
);
await
this
.
sdk
.
model
(
"general_order"
).
registerOnChange
(
this
.
onTransportMessage
);
if
(
this
.
listName
===
"group_receiving"
||
this
.
listName
===
"group_wait"
)
{
await
this
.
sdk
.
model
(
"UniplatChat"
)
.
registerOnChange
(
this
.
onTransportMessage
);
await
this
.
sdk
.
model
(
"general_order"
)
.
registerOnChange
(
this
.
onTransportMessage
);
if
(
this
.
listName
===
"group_receiving"
||
this
.
listName
===
"group_wait"
)
{
this
.
$eventHub
.
$on
(
EVENTS
.
ChatUpdate
,
this
.
refreshListDebounce
);
}
}
onTransportMessage
(
e
:
any
)
{
let
index
=
e
.
dataUpdates
.
findIndex
(
it
=>
it
.
action
===
"startChat"
||
it
.
action
===
"createChat"
||
it
.
action
===
"csExitChat"
||
it
.
action
===
"finishChat"
||
it
.
action
===
"delete"
&&
it
.
model
===
"general_order"
||
it
.
action
===
"sendMsg"
&&
this
.
listName
===
"group_before_handle"
&&
this
.
chatList
.
findIndex
(
chat
=>
chat
.
id
==
it
.
selectedList
[
0
])
>
-
1
let
index
=
e
.
dataUpdates
.
findIndex
(
(
it
)
=>
it
.
action
===
"startChat"
||
it
.
action
===
"createChat"
||
it
.
action
===
"csExitChat"
||
it
.
action
===
"finishChat"
||
(
it
.
action
===
"delete"
&&
it
.
model
===
"general_order"
)
||
(
it
.
action
===
"sendMsg"
&&
this
.
listName
===
"group_before_handle"
&&
this
.
chatList
.
findIndex
(
(
chat
)
=>
chat
.
id
==
it
.
selectedList
[
0
]
)
>
-
1
)
);
if
(
index
>
-
1
)
{
if
(
this
.
listName
===
"group_before_handle"
&&
e
.
dataUpdates
.
findIndex
(
it
=>
it
.
action
===
"sendMsg"
)
>
-
1
)
{
this
.
$eventHub
.
$emit
(
EVENTS
.
ChatUpdate
)
if
(
this
.
listName
===
"group_before_handle"
&&
e
.
dataUpdates
.
findIndex
((
it
)
=>
it
.
action
===
"sendMsg"
)
>
-
1
)
{
this
.
$eventHub
.
$emit
(
EVENTS
.
ChatUpdate
);
}
this
.
refreshListDebounce
();
}
...
...
@@ -208,13 +248,13 @@ export default class ModelChatList extends Vue {
private
refreshListDebounce
()
{
if
(
this
.
sseTs
)
{
return
return
;
}
this
.
sseTs
=
new
Date
().
getTime
();
setTimeout
(()
=>
{
this
.
sseTs
=
0
;
this
.
getList
();
},
1000
)
},
1000
)
;
}
mounted
()
{
...
...
@@ -222,16 +262,16 @@ export default class ModelChatList extends Vue {
}
private
handleSizeChange
(
newPageSize
)
{
this
.
pageSize
=
newPageSize
this
.
getList
()
this
.
pageSize
=
newPageSize
;
this
.
getList
()
;
}
private
async
goToChatRoom
(
data
:
ChatType
)
{
const
chatInfo
=
await
this
.
_createChat
({
modelName
:
data
.
business_data
.
model_name
,
selectedListId
:
data
.
business_data
.
obj_id
,
uids
:[],
showByPage
:
true
uids
:
[],
showByPage
:
true
,
});
this
.
activeId
=
data
.
chat_id
.
toString
();
}
...
...
@@ -243,10 +283,17 @@ export default class ModelChatList extends Vue {
private
parseMesage
(
data
:
ChatType
)
{
if
(
data
.
last_msg_sender
&&
data
.
last_msg_sender
!=
"0"
)
{
if
(
this
.
userNames
[
data
.
last_msg_sender
]
===
undefined
)
{
this
.
updateUserName
({
id
:
data
.
last_msg_sender
,
name
:
""
});
this
.
sdk
.
model
(
"user"
).
detail
(
data
.
last_msg_sender
).
query
().
then
(
userInfo
=>
{
this
.
updateUserName
({
id
:
data
.
last_msg_sender
,
name
:
userInfo
.
row
.
first_name
.
display
as
string
});
})
this
.
updateUserName
({
id
:
data
.
last_msg_sender
,
name
:
""
});
this
.
sdk
.
model
(
"user"
)
.
detail
(
data
.
last_msg_sender
)
.
query
()
.
then
((
userInfo
)
=>
{
this
.
updateUserName
({
id
:
data
.
last_msg_sender
,
name
:
userInfo
.
row
.
first_name
.
display
as
string
,
});
});
}
}
if
(
data
.
last_msg_content
===
""
)
return
"[暂无消息]"
;
...
...
@@ -264,19 +311,19 @@ export default class ModelChatList extends Vue {
}
private
batchStartReception
()
{
let
chats
=
this
.
chatRooms
.
filter
(
chat
=>
chat
.
checked
);
let
chats
=
this
.
chatRooms
.
filter
(
(
chat
)
=>
chat
.
checked
);
if
(
chats
.
length
===
0
)
{
this
.
$message
.
warning
(
"请先勾选要接待的会话"
)
return
this
.
$message
.
warning
(
"请先勾选要接待的会话"
)
;
return
;
}
chats
.
forEach
(
chat
=>
{
chats
.
forEach
(
(
chat
)
=>
{
const
{
model_name
,
obj_id
}
=
chat
.
business_data
;
this
.
sdk
.
model
(
model_name
)
.
chat
(
+
obj_id
,
this
.
global
.
org
.
id
.
toString
())
.
startChat
();
chat
.
checked
=
false
;
})
})
;
this
.
showItemCheckbox
=
false
;
this
.
clearActiveId
();
}
...
...
components/chat-list.vue
View file @
452addc8
...
...
@@ -26,14 +26,16 @@
:title=
"item.customer_name"
class=
"chat-name flex-fill text-dot-dot-dot"
>
<span>
{{
item
.
title
||
item
.
chat_id
}}
</span>
<span>
{{
item
.
title
||
item
.
chat_id
}}
</span>
</div>
<div
v-if=
"item.last_msg_ts"
class=
"chat-time"
>
{{
formatTimestamp
(
item
.
last_msg_ts
)
}}
</div>
</div>
<div
class=
"chat-msg text-dot-dot-dot"
>
{{
userNames
[
item
.
last_msg_sender
]
?
userNames
[
item
.
last_msg_sender
]
+
": "
:
""
}}{{
parseMe
sage
(
item
)
}}
{{
buildLastMes
sage
(
item
)
}}
</div>
</div>
</div>
...
...
@@ -52,63 +54,15 @@
<
script
lang=
"ts"
>
import
{
Component
,
Prop
,
Ref
,
Vue
}
from
"vue-property-decorator"
;
import
avatar
from
"@/customer-service/components/avatar.vue"
;
import
{
chatStore
,
ChatStore
}
from
"@/customer-service/store/model"
;
import
{
formatTime
,
TimeFormatRule
}
from
"@/customer-service/utils/time"
;
import
{
ChatStore
}
from
"@/customer-service/store/model"
;
import
{
Chat
as
ChatType
}
from
"@/customer-service/xim/models/chat"
;
import
{
EVENTS
}
from
"@/EventConsts"
export
function
parserMessage
(
type
:
string
,
rawMsg
:
string
)
{
if
(
!
type
)
return
""
;
if
(
!
rawMsg
)
return
""
;
const
msg
=
JSON
.
parse
(
rawMsg
);
if
(
type
===
"text"
)
{
return
msg
.
text
;
}
else
if
(
type
===
"image"
)
{
return
`[图片]`
;
}
else
if
(
type
===
"file"
)
{
return
`[文件]`
;
}
else
{
return
`[系统自动回复]`
;
}
}
import
{
EVENTS
}
from
"@/EventConsts"
;
import
Controller
from
"./controller/chat-list"
;
@
Component
({
components
:
{
avatar
}
})
export
default
class
ChatList
extends
Vue
{
export
default
class
ChatList
extends
Controller
{
private
searchKeyword
=
""
;
@
chatStore
.
Action
(
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
;
@
chatStore
.
State
(
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
)
private
readonly
chatList
!
:
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_USERNAME
)
private
readonly
userNames
!
:
ChatStore
.
STATE_CHAT_USERNAME
;
@
chatStore
.
Action
(
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
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SAVE_USERNAME
)
private
readonly
updateUserName
!
:
ChatStore
.
MUTATION_SAVE_USERNAME
;
@
chatStore
.
Mutation
(
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
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SHOW_CHAT
)
private
readonly
showChat
:
ChatStore
.
MUTATION_SHOW_CHAT
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_HIDE_CHAT
)
private
readonly
hideChat
:
ChatStore
.
MUTATION_HIDE_CHAT
;
@
Prop
({
type
:
Number
,
default
:
-
1
})
private
selected
!
:
number
;
...
...
@@ -118,11 +72,14 @@ export default class ChatList extends Vue {
private
unReadMsgCount
=
0
;
private
get
chatRooms
()
{
const
list
=
this
.
chatList
?.
list
.
filter
(
chat
=>
chat
.
title
.
indexOf
(
this
.
searchKeyword
)
>
-
1
)
||
[];
const
list
=
this
.
chatList
?.
list
.
filter
(
(
chat
)
=>
chat
.
title
.
indexOf
(
this
.
searchKeyword
)
>
-
1
)
||
[];
let
unReadMsgCount
=
0
;
list
.
filter
(
chat
=>
chat
.
unread_msg_count
>
0
).
forEach
(
chat
=>
{
unReadMsgCount
+=
chat
.
unread_msg_count
})
list
.
filter
(
(
chat
)
=>
chat
.
unread_msg_count
>
0
).
forEach
((
chat
)
=>
{
unReadMsgCount
+=
chat
.
unread_msg_count
;
})
;
this
.
unReadMsgCount
=
unReadMsgCount
;
this
.
$emit
(
"list-count-update"
,
this
.
unReadMsgCount
);
this
.
$eventHub
.
$emit
(
EVENTS
.
NewMsg
,
this
.
unReadMsgCount
);
...
...
@@ -159,7 +116,7 @@ export default class ChatList extends Vue {
this
.
showChat
();
this
.
close
();
if
(
data
.
unread_msg_count
>
0
)
{
data
.
unread_msg_count
=
0
;
}
...
...
@@ -169,23 +126,6 @@ export default class ChatList extends Vue {
this
.
$emit
(
"change"
);
}
private
parseMesage
(
data
:
ChatType
)
{
if
(
data
.
last_msg_sender
&&
data
.
last_msg_sender
!=
"0"
)
{
if
(
this
.
userNames
[
data
.
last_msg_sender
]
===
undefined
)
{
this
.
updateUserName
({
id
:
data
.
last_msg_sender
,
name
:
""
});
this
.
sdk
.
model
(
"user"
).
detail
(
data
.
last_msg_sender
).
query
().
then
(
userInfo
=>
{
this
.
updateUserName
({
id
:
data
.
last_msg_sender
,
name
:
userInfo
.
row
.
first_name
.
display
as
string
});
})
}
}
if
(
data
.
last_msg_content
===
""
)
return
"[暂无消息]"
;
return
parserMessage
(
data
.
last_msg_type
,
data
.
last_msg_content
);
}
private
formatTimestamp
(
v
:
number
)
{
return
formatTime
(
v
,
{
short
:
true
,
rule
:
TimeFormatRule
.
Hour12
});
}
private
close
()
{
this
.
$emit
(
"close"
);
}
...
...
components/chat-room.vue
View file @
452addc8
<
template
>
<div
class=
"chat-room-con h-100 pos-rel"
>
<div
class=
"chat-panel"
>
<div
class=
"chat-area h-100"
ref=
"chatBox"
>
<div
ref=
"top"
class=
"chat-messages pos-rel"
:class=
"
{'is-not-chat-member': !isChatMember}">
<div
class=
"chat-area h-100 d-flex flex-column"
ref=
"chatBox"
>
<div
ref=
"top"
class=
"chat-messages pos-rel flex-fill"
:class=
"
{ 'is-not-chat-member': !isChatMember }"
>
<div
v-if=
"getCurrentInputingPeople.length"
class=
"someone-inputing"
...
...
@@ -11,7 +15,13 @@
</div>
<messages
/>
</div>
<div
class=
"resize"
title=
"收缩侧边栏"
ref=
"resize"
@
mousedown=
"dragControllerDiv"
v-if=
"isChatMember"
></div>
<div
class=
"resize"
title=
"收缩侧边栏"
ref=
"resize"
@
mousedown=
"dragControllerDiv"
v-if=
"isChatMember"
></div>
<div
ref=
"bottom"
class=
"chat-input"
v-if=
"isChatMember"
>
<message-input
@
error=
"onError"
/>
</div>
...
...
@@ -20,7 +30,14 @@
</div>
</
template
>
<
script
lang=
"ts"
>
import
{
Component
,
Prop
,
Provide
,
Vue
,
Watch
,
Ref
}
from
"vue-property-decorator"
;
import
{
Component
,
Prop
,
Provide
,
Vue
,
Watch
,
Ref
,
}
from
"vue-property-decorator"
;
import
MessageInput
from
"@/customer-service/components/message-input.vue"
;
import
messages
from
"@/customer-service/components/message-list.vue"
;
...
...
@@ -106,7 +123,7 @@ export default class ChatRoom extends Vue {
this
.
$message
.
error
(
msg
);
}
private
dragControllerDiv
(
e
:
MouseEvent
)
{
private
dragControllerDiv
(
e
:
MouseEvent
)
{
let
resize
=
this
.
refResize
as
any
;
let
top
=
this
.
refTop
as
HTMLElement
;
let
bottom
=
this
.
refBottom
as
HTMLElement
;
...
...
@@ -126,9 +143,9 @@ export default class ChatRoom extends Vue {
}
let
bottomHeight
=
box
.
clientHeight
-
moveLen
;
resize
.
style
.
top
=
moveLen
+
'px'
;
// 设置左侧区域的宽度
top
.
style
.
height
=
moveLen
+
'px'
;
bottom
.
style
.
height
=
bottomHeight
+
'px'
;
resize
.
style
.
top
=
moveLen
+
"px"
;
// 设置左侧区域的宽度
top
.
style
.
height
=
moveLen
+
"px"
;
bottom
.
style
.
height
=
bottomHeight
+
"px"
;
};
// 鼠标松开事件
document
.
onmouseup
=
function
(
evt
)
{
...
...
components/chat-title.vue
View file @
452addc8
...
...
@@ -14,21 +14,36 @@
<div
v-else
class=
"chat-status"
>
接待中
</div>
</
template
>
</div>
<div
class=
"title-buttons"
>
<el-button
class=
"button"
@
click=
"startReception"
round
v-if=
"!isChatMember"
<div
class=
"title-buttons d-flex align-items-center"
>
<el-button
class=
"button"
@
click=
"startReception"
round
v-if=
"!isChatMember"
>
我要接待
</el-button
>
<el-button
class=
"button"
@
click=
"exitChat"
round
v-if=
"isChatMember"
<el-button
class=
"button"
@
click=
"exitChat"
round
v-if=
"isChatMember"
>
退出会话
</el-button
>
<el-button
class=
"button"
@
click=
"finishReception"
round
v-if=
"isChatMember && operatorType > 25"
<el-button
class=
"button"
@
click=
"finishReception"
round
v-if=
"isChatMember && operatorType > 25"
>
结束接待
</el-button
>
<el-button
class=
"button"
@
click=
"showAddMember"
round
>
添加客服
</el-button
>
<i
v-if=
"close && isSingleChat"
@
click=
"close"
class=
"title-close el-icon-close"
/>
<i
v-if=
"close && isSingleChat"
@
click=
"close"
class=
"title-close el-icon-close"
/>
</div>
<ChatCreator
v-if=
"visible"
...
...
@@ -115,9 +130,9 @@ export default class ChatTitle extends Vue {
private
async
exitChat
()
{
try
{
if
(
this
.
operatorType
==
'25'
)
{
if
(
this
.
operatorType
==
"25"
)
{
await
this
.
_userExitChat
();
}
else
if
(
+
this
.
operatorType
>
25
){
}
else
if
(
+
this
.
operatorType
>
25
)
{
await
this
.
_csExitChat
();
}
this
.
hideChat
();
...
...
@@ -129,18 +144,22 @@ export default class ChatTitle extends Vue {
private
async
startReception
()
{
try
{
await
this
.
_startReception
();
this
.
$emit
(
"updateActive"
,
"my_receiving"
)
this
.
$emit
(
"updateActive"
,
"my_receiving"
)
;
}
catch
(
error
)
{
console
.
error
(
error
);
}
}
private
async
finishReception
()
{
await
this
.
$confirm
(
"确定要结束接待吗?结束接待将会终止客服会话"
,
"提示"
,
{
confirmButtonText
:
"确定"
,
cancelButtonText
:
"取消"
,
type
:
"warning"
,
})
await
this
.
$confirm
(
"确定要结束接待吗?结束接待将会终止客服会话"
,
"提示"
,
{
confirmButtonText
:
"确定"
,
cancelButtonText
:
"取消"
,
type
:
"warning"
,
}
);
await
this
.
_finishReception
();
this
.
hideChat
();
}
...
...
@@ -167,7 +186,7 @@ export default class ChatTitle extends Vue {
.title-close
{
color
:
#8d959d
;
cursor
:
pointer
;
margin-left
:
30px
;
margin-left
:
30px
;
}
}
</
style
>
components/controller/chat-list.ts
0 → 100644
View file @
452addc8
import
{
Component
,
Vue
}
from
"vue-property-decorator"
;
import
{
chatStore
,
ChatStore
}
from
"@/customer-service/store/model"
;
import
{
parserMessage
}
from
"."
;
import
{
Chat
as
ChatItem
}
from
"@/customer-service/xim/models/chat"
;
import
{
formatTime
,
TimeFormatRule
}
from
"@/customer-service/utils/time"
;
@
Component
({
components
:
{}
})
export
default
class
ChatList
extends
Vue
{
@
chatStore
.
Action
(
ChatStore
.
ACTION_GET_MY_CHAT_LIST
)
protected
readonly
getMyChatList
!
:
ChatStore
.
ACTION_GET_MY_CHAT_LIST
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
protected
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
)
protected
readonly
chatList
!
:
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_USERNAME
)
protected
readonly
userNames
!
:
ChatStore
.
STATE_CHAT_USERNAME
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
)
protected
readonly
saveChatId
!
:
ChatStore
.
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SAVE_MYSELF_ID
)
protected
readonly
saveMyId
!
:
ChatStore
.
MUTATION_SAVE_MYSELF_ID
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SAVE_USERNAME
)
protected
readonly
updateUserName
!
:
ChatStore
.
MUTATION_SAVE_USERNAME
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SET_CHAT_SOURCE
)
protected
readonly
setSource
!
:
ChatStore
.
MUTATION_SET_CHAT_SOURCE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SAVE_CHAT_TITLE
)
protected
readonly
saveChatTitle
!
:
ChatStore
.
MUTATION_SAVE_CHAT_TITLE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SHOW_CHAT
)
protected
readonly
showChat
:
ChatStore
.
MUTATION_SHOW_CHAT
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_HIDE_CHAT
)
protected
readonly
hideChat
:
ChatStore
.
MUTATION_HIDE_CHAT
;
protected
parseMesage
(
data
:
ChatItem
)
{
if
(
data
.
last_msg_sender
&&
data
.
last_msg_sender
!=
"0"
)
{
if
(
this
.
userNames
[
data
.
last_msg_sender
]
===
undefined
)
{
this
.
updateUserName
({
id
:
data
.
last_msg_sender
,
name
:
""
});
this
.
sdk
.
model
(
"user"
)
.
detail
(
data
.
last_msg_sender
)
.
query
()
.
then
((
userInfo
)
=>
{
this
.
updateUserName
({
id
:
data
.
last_msg_sender
,
name
:
userInfo
.
row
.
first_name
.
display
as
string
,
});
});
}
}
if
(
data
.
last_msg_content
===
""
)
{
return
"[暂无消息]"
;
}
return
parserMessage
(
data
.
last_msg_type
,
data
.
last_msg_content
);
}
protected
formatTimestamp
(
v
:
number
)
{
return
formatTime
(
v
,
{
short
:
true
,
rule
:
TimeFormatRule
.
Hour12
});
}
protected
buildLastMessage
(
item
:
ChatItem
)
{
const
sender
=
this
.
userNames
[
item
.
last_msg_sender
];
if
(
sender
)
{
return
`
${
sender
}
:
${
this
.
parseMesage
(
item
)}
`
;
}
return
this
.
parseMesage
(
item
);
}
}
components/controller/index.ts
0 → 100644
View file @
452addc8
export
function
parserMessage
(
type
:
string
,
rawMsg
:
string
)
{
if
(
!
type
)
return
""
;
if
(
!
rawMsg
)
return
""
;
const
msg
=
JSON
.
parse
(
rawMsg
);
if
(
type
===
"text"
)
{
return
msg
.
text
;
}
else
if
(
type
===
"image"
)
{
return
`[图片]`
;
}
else
if
(
type
===
"file"
)
{
return
`[文件]`
;
}
else
{
return
`[系统自动回复]`
;
}
}
components/message-input.vue
View file @
452addc8
<
template
>
<div
class=
"h-100"
>
<chat-input
ref=
"chat-input"
@
input=
"onInput"
@
send=
"sendMessage"
@
error=
"onError"
/>
</div>
<div
class=
"h-100"
>
<chat-input
ref=
"chat-input"
@
input=
"onInput"
@
send=
"sendMessage"
@
error=
"onError"
/>
</div>
</
template
>
<
script
lang=
"ts"
>
import
{
Component
,
Ref
,
Vue
,
Watch
}
from
"vue-property-decorator"
;
import
ChatInput
,
{
FILE_INFO_CLASS
,
isImageOrFile
,
FILE_INFO_CLASS
,
isImageOrFile
,
}
from
"../hybrid-input/index.vue"
;
import
{
Message
}
from
"../model"
;
import
{
uploadFile
}
from
"../service/upload"
;
...
...
@@ -26,167 +26,169 @@ 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
;
@
chatStore
.
State
(
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
;
@
chatStore
.
Action
(
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
;
@
chatStore
.
State
(
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
;
@
chatStore
.
State
(
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
;
@
chatStore
.
State
(
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
;
@
chatStore
.
Getter
(
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
;
@
chatStore
.
Mutation
(
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
;
@
chatStore
.
Mutation
(
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
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_REMOVE_SENDING_MESSAGE
)
private
readonly
removeSendingMessages
!
:
ChatStore
.
MUTATION_REMOVE_SENDING_MESSAGE
;
@
Ref
(
"chat-input"
)
private
readonly
chatInput
!
:
ChatInput
;
@
Ref
(
"chat-input"
)
private
readonly
chatInput
!
:
ChatInput
;
@
Watch
(
"chatRoomVisible"
)
private
whenChatRoomShow
()
{
if
(
!
this
.
chatRoomVisible
)
return
;
this
.
chatInput
.
focus
();
}
private
async
sendMessage
(
msg
:
ChildNode
[],
done
:
()
=>
void
)
{
if
(
this
.
chatIniting
)
{
return
;
@
Watch
(
"chatRoomVisible"
)
private
whenChatRoomShow
()
{
if
(
!
this
.
chatRoomVisible
)
return
;
this
.
chatInput
.
focus
();
}
for
(
const
item
of
msg
)
{
if
(
isImageOrFile
(
item
))
{
if
((
item
as
Element
).
classList
.
contains
(
FILE_INFO_CLASS
))
{
this
.
sendFile
(
item
,
"file"
);
}
else
{
this
.
sendFile
(
item
,
"image"
);
private
async
sendMessage
(
msg
:
ChildNode
[],
done
:
()
=>
void
)
{
if
(
this
.
chatIniting
)
{
return
;
}
continue
;
}
for
(
const
item
of
msg
)
{
if
(
isImageOrFile
(
item
))
{
if
((
item
as
Element
).
classList
.
contains
(
FILE_INFO_CLASS
))
{
this
.
sendFile
(
item
,
"file"
);
}
else
{
this
.
sendFile
(
item
,
"image"
);
}
continue
;
}
if
(
item
.
textContent
)
{
this
.
sendText
(
item
.
textContent
);
}
if
(
item
.
textContent
)
{
this
.
sendText
(
item
.
textContent
);
}
}
ChatLoggerService
.
logger
?.
debug
(
"all messages sent"
);
done
();
this
.
$emit
(
"sent"
);
}
ChatLoggerService
.
logger
?.
debug
(
"all messages sent"
);
done
();
this
.
$emit
(
"sent"
);
}
private
async
onInput
()
{
if
(
this
.
chatId
==
null
)
return
;
await
xim
.
inputing
(
this
.
chatId
);
}
private
sendText
(
text
:
string
)
{
if
(
text
&&
text
.
trim
())
{
const
msg
=
{
text
:
text
.
trim
()
};
if
(
this
.
source
)
{
Object
.
assign
(
msg
,
{
source
:
this
.
source
});
}
this
.
sendMsg
({
msgType
:
"text"
,
msg
:
JSON
.
stringify
(
msg
)
});
private
async
onInput
()
{
if
(
this
.
chatId
==
null
)
return
;
await
xim
.
inputing
(
this
.
chatId
);
}
}
private
async
sendFile
(
file
:
any
,
type
:
"image"
|
"file"
)
{
const
src
=
JSON
.
parse
(
file
.
attributes
[
`data-
${
type
}
`
]?.
value
||
""
)
as
{
url
:
string
;
name
:
string
;
size
:
number
;
};
if
(
src
)
{
const
index
=
this
.
sendSendingMessage
(
type
,
src
);
const
file
=
await
this
.
readBlobUrl2Base64
(
src
.
url
,
src
.
name
);
if
(
file
)
{
let
w
=
0
;
let
h
=
0
;
if
(
type
===
"image"
)
{
const
img
=
new
Image
();
img
.
src
=
src
.
url
;
img
.
onload
=
function
()
{
w
=
img
.
naturalWidth
;
h
=
img
.
naturalHeight
;
};
img
.
remove
();
}
uploadFile
(
file
)
.
then
((
r
)
=>
{
if
(
r
)
{
const
msg
=
{
url
:
r
,
name
:
file
.
name
,
size
:
file
.
size
,
};
if
(
this
.
source
)
{
Object
.
assign
(
msg
,
{
source
:
this
.
source
});
}
if
(
w
&&
h
)
{
Object
.
assign
(
msg
,
{
w
,
h
});
}
private
sendText
(
text
:
string
)
{
if
(
text
&&
text
.
trim
())
{
const
msg
=
{
text
:
text
.
trim
()
};
if
(
this
.
source
)
{
Object
.
assign
(
msg
,
{
source
:
this
.
source
});
}
this
.
sendMsg
({
msgType
:
"text"
,
msg
:
JSON
.
stringify
(
msg
)
});
}
}
this
.
sendMsg
({
msgType
:
type
,
msg
:
JSON
.
stringify
(
msg
),
});
this
.
removeSendingMessages
(
index
);
URL
.
revokeObjectURL
(
src
.
url
);
}
else
{
this
.
setMsg2Failed
(
index
);
private
async
sendFile
(
file
:
any
,
type
:
"image"
|
"file"
)
{
const
src
=
JSON
.
parse
(
file
.
attributes
[
`data-
${
type
}
`
]?.
value
||
""
)
as
{
url
:
string
;
name
:
string
;
size
:
number
;
};
if
(
src
)
{
const
index
=
this
.
sendSendingMessage
(
type
,
src
);
const
file
=
await
this
.
readBlobUrl2Base64
(
src
.
url
,
src
.
name
);
if
(
file
)
{
let
w
=
0
;
let
h
=
0
;
if
(
type
===
"image"
)
{
const
img
=
new
Image
();
img
.
src
=
src
.
url
;
img
.
onload
=
function
()
{
w
=
img
.
naturalWidth
;
h
=
img
.
naturalHeight
;
};
img
.
remove
();
}
uploadFile
(
file
)
.
then
((
r
)
=>
{
if
(
r
)
{
const
msg
=
{
url
:
r
,
name
:
file
.
name
,
size
:
file
.
size
,
};
if
(
this
.
source
)
{
Object
.
assign
(
msg
,
{
source
:
this
.
source
});
}
if
(
w
&&
h
)
{
Object
.
assign
(
msg
,
{
w
,
h
});
}
this
.
sendMsg
({
msgType
:
type
,
msg
:
JSON
.
stringify
(
msg
),
});
this
.
removeSendingMessages
(
index
);
URL
.
revokeObjectURL
(
src
.
url
);
}
else
{
this
.
setMsg2Failed
(
index
);
}
})
.
catch
((
e
)
=>
{
// eslint-disable-next-line no-console
console
.
error
(
e
);
this
.
setMsg2Failed
(
index
);
});
}
})
.
catch
((
e
)
=>
{
// eslint-disable-next-line no-console
console
.
error
(
e
);
this
.
setMsg2Failed
(
index
);
});
}
}
}
private
setMsg2Failed
(
index
:
number
)
{
this
.
failedSendingMessage
(
index
);
}
}
private
setMsg2Failed
(
index
:
number
)
{
this
.
failedSendingMessage
(
index
);
}
private
sendSendingMessage
(
type
:
string
,
msg
:
any
)
{
const
index
=
sendingMessageIndex
++
;
if
(
this
.
source
)
{
Object
.
assign
(
msg
,
{
source
:
this
.
source
,
eid
:
this
.
chatMyId
});
}
if
(
this
.
chatId
)
{
this
.
appendSendingMessages
({
id
:
-
index
,
chat_id
:
this
.
chatId
,
ts
:
Date
.
now
(),
type
,
msg
:
JSON
.
stringify
(
msg
),
}
as
Message
);
return
-
index
;
}
return
0
;
}
private
sendSendingMessage
(
type
:
string
,
msg
:
any
)
{
const
index
=
sendingMessageIndex
++
;
if
(
this
.
source
)
{
Object
.
assign
(
msg
,
{
source
:
this
.
source
,
eid
:
this
.
chatMyId
}
);
private
readBlobUrl2Base64
(
url
:
string
,
name
:
string
)
{
return
fetch
(
url
)
.
then
((
r
)
=>
r
.
blob
())
.
then
((
blob
)
=>
new
File
([
blob
],
name
)
);
}
if
(
this
.
chatId
)
{
this
.
appendSendingMessages
({
id
:
-
index
,
chat_id
:
this
.
chatId
,
ts
:
Date
.
now
(),
type
,
msg
:
JSON
.
stringify
(
msg
),
}
as
Message
);
return
-
index
;
private
onError
(
e
:
any
)
{
this
.
$emit
(
"error"
,
e
.
message
||
e
);
}
return
0
;
}
private
readBlobUrl2Base64
(
url
:
string
,
name
:
string
)
{
return
fetch
(
url
)
.
then
((
r
)
=>
r
.
blob
())
.
then
((
blob
)
=>
new
File
([
blob
],
name
));
}
private
onError
(
e
:
any
)
{
this
.
$emit
(
"error"
,
e
.
message
||
e
);
}
}
</
script
>
hybrid-input/index.vue
View file @
452addc8
<
template
>
<div
class=
"input-wrap h-100"
>
<div
class=
"tool-bar"
>
<img
class=
"tool-bar-icon"
title=
"发送表情"
@
click
.
stop=
"toggleEmoji"
src=
"@/customer-service/imgs/emoji.png"
/>
<label
for=
"chat-upload-file"
:title=
"tip4Image"
@
click=
"allowLoadImg"
class=
"offset"
>
<img
class=
"tool-bar-icon"
src=
"@/customer-service/imgs/pic.png"
/>
</label>
<!--
<label
for=
"chat-upload-file"
:title=
"tip4File"
@
click=
"allowLoadFile"
>
<div
class=
"input-wrap h-100"
>
<div
class=
"tool-bar"
>
<img
class=
"tool-bar-icon"
title=
"发送表情"
@
click
.
stop=
"toggleEmoji"
src=
"@/customer-service/imgs/emoji.png"
/>
<label
for=
"chat-upload-file"
:title=
"tip4Image"
@
click=
"allowLoadImg"
class=
"offset"
>
<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>
-->
<input
@
change=
"onChange"
:value=
"file"
id=
"chat-upload-file"
type=
"file"
:accept=
"acceptType"
multiple
/>
<input
@
change=
"onChange"
:value=
"file"
id=
"chat-upload-file"
type=
"file"
:accept=
"acceptType"
multiple
/>
</div>
<el-scrollbar
class=
"input-el-scrollbar"
>
<div
class=
"input-container"
ref=
"input"
id=
"chat-input-box"
contenteditable=
"true"
@
input=
"$emit('input')"
@
keypress
.
enter=
"handleSendMsg"
@
keyup
.
enter=
"handleReturn"
></div>
</el-scrollbar>
<div
class=
"emoji-picker"
v-show=
"emojiPanelVisibility"
>
<el-scrollbar
class=
"overflow-x-hidden"
>
<span
class=
"emoji-item"
v-for=
"item in emoji"
:key=
"item.code"
:title=
"item.name"
@
click
.
stop=
"selectEmoji(item)"
>
{{
item
.
emoji_chars
}}
</span
>
</el-scrollbar>
</div>
</div>
<el-scrollbar
class=
"input-el-scrollbar"
>
<div
class=
"input-container"
ref=
"input"
id=
"chat-input-box"
contenteditable=
"true"
@
input=
"$emit('input')"
@
keypress
.
enter=
"handleSendMsg"
@
keyup
.
enter=
"handleReturn"
></div>
</el-scrollbar>
<div
class=
"emoji-picker"
v-show=
"emojiPanelVisibility"
>
<el-scrollbar
class=
"overflow-x-hidden"
>
<span
class=
"emoji-item"
v-for=
"item in emoji"
:key=
"item.code"
:title=
"item.name"
@
click
.
stop=
"selectEmoji(item)"
>
{{
item
.
emoji_chars
}}
</span
>
</el-scrollbar>
</div>
</div>
</
template
>
<
script
lang=
"ts"
>
...
...
@@ -61,37 +64,37 @@ import { Component, Ref, Vue, Watch } from "vue-property-decorator";
import
{
namespace
}
from
"vuex-class"
;
import
{
getFileType
,
getSvg
,
MAX_FILE_SIZE
,
MAX_FILE_SIZE_STRING
,
MAX_IMAGE_SIZE
,
MAX_IMAGE_SIZE_STRING
,
MESSAGE_FILE_EMPTY
,
MESSAGE_FILE_TOO_LARGE
,
MESSAGE_IMAGE_TOO_LARGE
,
getFileType
,
getSvg
,
MAX_FILE_SIZE
,
MAX_FILE_SIZE_STRING
,
MAX_IMAGE_SIZE
,
MAX_IMAGE_SIZE_STRING
,
MESSAGE_FILE_EMPTY
,
MESSAGE_FILE_TOO_LARGE
,
MESSAGE_IMAGE_TOO_LARGE
,
}
from
"../components/file-controller"
;
import
{
EmojiService
}
from
"../service/emoji"
;
import
{
ChatStore
}
from
"../store/model"
;
import
{
formatFileSize
}
from
"../utils"
;
export
const
enum
InputMessageType
{
Text
=
"text"
,
Image
=
"image"
,
File
=
"file"
,
Text
=
"text"
,
Image
=
"image"
,
File
=
"file"
,
}
export
interface
InputMessageBody
{
text
?:
string
;
url
?:
string
;
name
?:
string
;
size
?:
number
;
text
?:
string
;
url
?:
string
;
name
?:
string
;
size
?:
number
;
}
export
interface
InputMessage
{
type
:
InputMessageType
;
body
:
InputMessageBody
;
file
?:
File
|
null
;
type
:
InputMessageType
;
body
:
InputMessageBody
;
file
?:
File
|
null
;
}
const
chatStoreNamespace
=
namespace
(
"chatStore"
);
...
...
@@ -102,560 +105,566 @@ export const IMAGE_INFO_CLASS = "img-info";
export
const
FILE_INFO_CLASS
=
"file-info"
;
export
function
isImageOrFile
(
node
:
ChildNode
)
{
const
e
=
node
as
HTMLElement
;
return
(
e
.
classList
&&
(
e
.
classList
.
contains
(
IMAGE_INFO_CLASS
)
||
e
.
classList
.
contains
(
FILE_INFO_CLASS
))
);
const
e
=
node
as
HTMLElement
;
return
(
e
.
classList
&&
(
e
.
classList
.
contains
(
IMAGE_INFO_CLASS
)
||
e
.
classList
.
contains
(
FILE_INFO_CLASS
))
);
}
@
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
;
@
chatStoreNamespace
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
Ref
(
"input"
)
private
readonly
messageInputBox
!
:
HTMLDivElement
;
private
file
=
""
;
private
acceptType
=
"image/*"
;
private
emojiPanelVisibility
=
false
;
@
Ref
(
"input"
)
private
readonly
messageInputBox
!
:
HTMLDivElement
;
private
tip4Image
=
`发送图片(最大
${
MAX_IMAGE_SIZE_STRING
}
)`
;
private
tip4File
=
`发送文件(最大
${
MAX_FILE_SIZE_STRING
}
)`
;
private
file
=
""
;
private
acceptType
=
"image/*"
;
private
emojiPanelVisibility
=
false
;
private
emoji
:
{
name
:
string
;
emoji_chars
:
string
;
code
:
string
}[]
=
[];
@
Watch
(
"chatId"
)
private
onChatIdChanged
(
v
:
number
,
old
:
number
)
{
if
(
old
)
{
const
current
=
this
.
getNodeListFromInputBox
();
if
(
current
&&
current
.
length
)
{
chatCache
[
old
]
=
current
;
}
}
private
tip4Image
=
`发送图片(最大
${
MAX_IMAGE_SIZE_STRING
}
)`
;
private
tip4File
=
`发送文件(最大
${
MAX_FILE_SIZE_STRING
}
)`
;
this
.
clearInput
();
if
(
v
)
{
const
cache
=
chatCache
[
v
];
if
(
cache
)
{
const
e
=
document
.
querySelector
(
"#chat-input-box"
)
as
HTMLElement
;
if
(
e
)
{
for
(
const
node
of
cache
as
ChildNode
[])
{
e
.
appendChild
(
node
);
}
}
}
}
}
private
emoji
:
{
name
:
string
;
emoji_chars
:
string
;
code
:
string
}[]
=
[];
public
mounted
()
{
this
.
messageInputBox
.
addEventListener
(
"paste"
,
this
.
handlePasteEvent
);
document
.
addEventListener
(
"click"
,
this
.
hideEmoji
);
document
.
addEventListener
(
"selectionchange"
,
this
.
handleSaveRange
);
this
.
setupEmoji
();
this
.
focus
();
}
@
Watch
(
"chatId"
)
private
onChatIdChanged
(
v
:
number
,
old
:
number
)
{
if
(
old
)
{
const
current
=
this
.
getNodeListFromInputBox
();
if
(
current
&&
current
.
length
)
{
chatCache
[
old
]
=
current
;
}
public
beforeDestroy
()
{
document
.
removeEventListener
(
"click"
,
this
.
hideEmoji
);
document
.
removeEventListener
(
"selectionchange"
,
this
.
handleSaveRange
);
}
this
.
clearInput
();
public
focus
()
{
this
.
messageInputBox
.
focus
();
}
if
(
v
)
{
const
cache
=
chatCache
[
v
];
if
(
cache
)
{
const
e
=
document
.
querySelector
(
"#chat-input-box"
)
as
HTMLElement
;
if
(
e
)
{
for
(
const
node
of
cache
as
ChildNode
[])
{
e
.
appendChild
(
node
);
}
private
clearInput
()
{
this
.
messageInputBox
.
innerHTML
=
""
;
const
input
=
document
.
getElementById
(
"chat-upload-file"
)
as
HTMLInputElement
;
if
(
input
)
{
input
.
value
=
""
;
}
}
}
}
public
mounted
()
{
this
.
messageInputBox
.
addEventListener
(
"paste"
,
this
.
handlePasteEvent
);
document
.
addEventListener
(
"click"
,
this
.
hideEmoji
);
document
.
addEventListener
(
"selectionchange"
,
this
.
handleSaveRange
);
this
.
setupEmoji
();
this
.
focus
();
}
public
beforeDestroy
()
{
document
.
removeEventListener
(
"click"
,
this
.
hideEmoji
);
document
.
removeEventListener
(
"selectionchange"
,
this
.
handleSaveRange
);
}
public
focus
()
{
this
.
messageInputBox
.
focus
();
}
private
clearInput
()
{
this
.
messageInputBox
.
innerHTML
=
""
;
const
input
=
document
.
getElementById
(
"chat-upload-file"
)
as
HTMLInputElement
;
if
(
input
)
{
input
.
value
=
""
;
private
allowLoadImg
()
{
this
.
acceptType
=
"image/*"
;
}
}
private
allowLoadImg
()
{
this
.
acceptType
=
"image/*"
;
}
private
allowLoadFile
()
{
this
.
acceptType
=
"*"
;
}
private
async
handlePasteEvent
(
event
:
ClipboardEvent
)
{
/*
* 组织默认行为原因
* 1、浏览器自带复制粘贴图片到输入框的功能,与js加工后的图片重复了,
* 2、默认复制粘贴功能会粘贴dom结构
* */
event
.
preventDefault
();
const
items
=
event
.
clipboardData
&&
event
.
clipboardData
.
items
;
let
html
=
""
;
const
promiseArr
=
[];
if
(
items
&&
items
.
length
)
{
// 检索剪切板items中类型带有image的
for
(
let
i
=
0
;
i
<
items
.
length
;
i
++
)
{
if
(
items
[
i
].
kind
===
"file"
)
{
const
file
=
items
[
i
].
getAsFile
();
if
(
file
)
{
if
(
file
.
size
<=
0
)
{
this
.
$emit
(
"error"
,
MESSAGE_FILE_EMPTY
);
return
;
}
if
(
this
.
isImage
(
file
))
{
if
(
file
.
size
>=
MAX_IMAGE_SIZE
)
{
this
.
$emit
(
"error"
,
MESSAGE_IMAGE_TOO_LARGE
);
return
;
}
html
+=
this
.
buildImageHtml
(
file
);
}
else
{
if
(
file
.
size
>=
MAX_FILE_SIZE
)
{
this
.
$emit
(
"error"
,
MESSAGE_FILE_TOO_LARGE
);
return
;
}
html
+=
this
.
buildFileHtml
(
file
);
}
break
;
}
}
else
{
promiseArr
.
push
(
new
Promise
<
void
>
((
resolve
)
=>
{
const
contentType
=
items
[
i
].
type
;
items
[
i
].
getAsString
((
k
)
=>
{
/*
* items第一项是文本
* 第二项是带有dom结构的文本(包含格式)
* 第三项写明了数据是从哪里复制来的
*/
if
(
i
===
0
)
{
if
(
contentType
===
"text/plain"
)
{
html
+=
k
;
}
}
else
if
(
i
===
1
)
{
const
srcRegex
=
/<img
[^
>
]
+src="
([^
">
]
+
)
"
\s
+title="
([^
">
]
+
)
"/g
;
let
result
;
do
{
result
=
srcRegex
.
exec
(
k
);
if
(
result
)
{
const
[,
src
,
name
]
=
result
;
html
+=
`<img tabindex="-1" src="
${
src
}
" data-image='
${
JSON
.
stringify
(
{
url
:
src
,
name
,
private
allowLoadFile
()
{
this
.
acceptType
=
"*"
;
}
private
async
handlePasteEvent
(
event
:
ClipboardEvent
)
{
/*
* 组织默认行为原因
* 1、浏览器自带复制粘贴图片到输入框的功能,与js加工后的图片重复了,
* 2、默认复制粘贴功能会粘贴dom结构
* */
event
.
preventDefault
();
const
items
=
event
.
clipboardData
&&
event
.
clipboardData
.
items
;
let
html
=
""
;
const
promiseArr
=
[];
if
(
items
&&
items
.
length
)
{
// 检索剪切板items中类型带有image的
for
(
let
i
=
0
;
i
<
items
.
length
;
i
++
)
{
if
(
items
[
i
].
kind
===
"file"
)
{
const
file
=
items
[
i
].
getAsFile
();
if
(
file
)
{
if
(
file
.
size
<=
0
)
{
this
.
$emit
(
"error"
,
MESSAGE_FILE_EMPTY
);
return
;
}
if
(
this
.
isImage
(
file
))
{
if
(
file
.
size
>=
MAX_IMAGE_SIZE
)
{
this
.
$emit
(
"error"
,
MESSAGE_IMAGE_TOO_LARGE
);
return
;
}
html
+=
this
.
buildImageHtml
(
file
);
}
else
{
if
(
file
.
size
>=
MAX_FILE_SIZE
)
{
this
.
$emit
(
"error"
,
MESSAGE_FILE_TOO_LARGE
);
return
;
}
html
+=
this
.
buildFileHtml
(
file
);
}
)}
'>`
;
html
=
html
.
replace
(
name
,
""
);
break
;
}
}
while
(
result
);
}
else
{
promiseArr
.
push
(
new
Promise
<
void
>
((
resolve
)
=>
{
const
contentType
=
items
[
i
].
type
;
items
[
i
].
getAsString
((
k
)
=>
{
/*
* items第一项是文本
* 第二项是带有dom结构的文本(包含格式)
* 第三项写明了数据是从哪里复制来的
*/
if
(
i
===
0
)
{
if
(
contentType
===
"text/plain"
)
{
html
+=
k
;
}
}
else
if
(
i
===
1
)
{
const
srcRegex
=
/<img
[^
>
]
+src="
([^
">
]
+
)
"
\s
+title="
([^
">
]
+
)
"/g
;
let
result
;
do
{
result
=
srcRegex
.
exec
(
k
);
if
(
result
)
{
const
[,
src
,
name
]
=
result
;
html
+=
`<img tabindex="-1" src="
${
src
}
" data-image='
${
JSON
.
stringify
(
{
url
:
src
,
name
,
}
)}
'>`
;
html
=
html
.
replace
(
name
,
""
);
}
}
while
(
result
);
}
resolve
();
});
})
);
}
resolve
();
});
})
);
}
}
await
Promise
.
all
(
promiseArr
);
if
(
html
)
{
this
.
insertHtmlAtCaret
(
html
);
}
}
}
await
Promise
.
all
(
promiseArr
);
if
(
html
)
{
this
.
insertHtmlAtCaret
(
html
);
}
}
private
handleReturn
(
e
:
KeyboardEvent
)
{
if
(
e
.
altKey
||
e
.
ctrlKey
)
{
let
el
=
this
.
messageInputBox
let
range
=
document
.
createRange
()
let
sel
=
window
.
getSelection
()
let
offset
=
sel
.
focusOffset
let
content
=
el
.
innerHTML
el
.
innerHTML
=
content
.
slice
(
0
,
offset
)
+
'\n'
+
(
content
.
slice
(
offset
)
||
'\n'
)
range
.
setStart
(
el
.
childNodes
[
0
],
offset
+
1
)
range
.
collapse
(
true
)
sel
.
removeAllRanges
()
sel
.
addRange
(
range
)
return
private
handleReturn
(
e
:
KeyboardEvent
)
{
if
(
e
.
altKey
||
e
.
ctrlKey
)
{
let
el
=
this
.
messageInputBox
;
let
range
=
document
.
createRange
();
let
sel
=
window
.
getSelection
();
let
offset
=
sel
.
focusOffset
;
let
content
=
el
.
innerHTML
;
el
.
innerHTML
=
content
.
slice
(
0
,
offset
)
+
"\n"
+
(
content
.
slice
(
offset
)
||
"\n"
);
range
.
setStart
(
el
.
childNodes
[
0
],
offset
+
1
);
range
.
collapse
(
true
);
sel
.
removeAllRanges
();
sel
.
addRange
(
range
);
return
;
}
if
(
e
.
shiftKey
)
{
e
.
preventDefault
();
return
;
}
}
if
(
e
.
shiftKey
)
{
e
.
preventDefault
();
return
private
async
handleSendMsg
(
e
:
KeyboardEvent
)
{
e
.
preventDefault
();
if
(
e
.
shiftKey
||
e
.
ctrlKey
||
e
.
altKey
)
{
return
;
}
return
new
Promise
((
resolve
,
reject
)
=>
{
try
{
const
data
=
this
.
getNodeListFromInputBox
();
this
.
$emit
(
"send"
,
data
,
resolve
);
}
catch
(
e
)
{
this
.
$emit
(
"error"
,
e
);
reject
(
e
);
}
}).
then
(()
=>
{
this
.
clearInput
();
if
(
this
.
chatId
)
{
chatCache
[
this
.
chatId
]
=
[];
}
});
}
}
private
async
handleSendMsg
(
e
:
KeyboardEvent
)
{
e
.
preventDefault
();
if
(
e
.
shiftKey
||
e
.
ctrlKey
||
e
.
altKey
)
{
return
/**
* 获取输入框中的内容
* @returns 返回的是节点数组
*/
private
getNodeListFromInputBox
()
{
this
.
messageInputBox
.
normalize
();
const
nodes
=
Array
.
from
(
this
.
messageInputBox
.
childNodes
);
return
this
.
combine
(
nodes
);
}
return
new
Promise
((
resolve
,
reject
)
=>
{
try
{
const
data
=
this
.
getNodeListFromInputBox
();
this
.
$emit
(
"send"
,
data
,
resolve
);
}
catch
(
e
)
{
this
.
$emit
(
"error"
,
e
);
reject
(
e
);
}
}).
then
(()
=>
{
this
.
clearInput
();
if
(
this
.
chatId
)
{
chatCache
[
this
.
chatId
]
=
[];
}
});
}
/**
* 获取输入框中的内容
* @returns 返回的是节点数组
*/
private
getNodeListFromInputBox
()
{
this
.
messageInputBox
.
normalize
();
const
nodes
=
Array
.
from
(
this
.
messageInputBox
.
childNodes
);
return
this
.
combine
(
nodes
);
}
/**
* 文本,链接等需要合并成纯文本发送
*/
private
combine
(
nodes
:
ChildNode
[])
{
const
sendingNodes
:
ChildNode
[]
=
[];
let
needCreateNewNode
=
false
;
let
text
=
""
;
for
(
const
item
of
nodes
)
{
if
(
!
isImageOrFile
(
item
)
&&
item
.
textContent
)
{
if
(
needCreateNewNode
)
{
text
=
""
;
needCreateNewNode
=
false
;
/**
* 文本,链接等需要合并成纯文本发送
*/
private
combine
(
nodes
:
ChildNode
[])
{
const
sendingNodes
:
ChildNode
[]
=
[];
let
needCreateNewNode
=
false
;
let
text
=
""
;
for
(
const
item
of
nodes
)
{
if
(
!
isImageOrFile
(
item
)
&&
item
.
textContent
)
{
if
(
needCreateNewNode
)
{
text
=
""
;
needCreateNewNode
=
false
;
}
text
+=
item
.
textContent
;
}
else
{
needCreateNewNode
=
true
;
if
(
text
)
{
this
.
checkTextLength
(
text
);
const
node
=
document
.
createTextNode
(
text
);
sendingNodes
.
push
(
node
);
}
sendingNodes
.
push
(
item
);
}
}
text
+=
item
.
textContent
;
}
else
{
needCreateNewNode
=
true
;
if
(
text
)
{
this
.
checkTextLength
(
text
);
const
node
=
document
.
createTextNode
(
text
);
sendingNodes
.
push
(
node
);
this
.
checkTextLength
(
text
);
const
node
=
document
.
createTextNode
(
text
);
sendingNodes
.
push
(
node
);
}
sendingNodes
.
push
(
item
);
}
return
sendingNodes
;
}
if
(
text
)
{
this
.
checkTextLength
(
text
);
const
node
=
document
.
createTextNode
(
text
);
sendingNodes
.
push
(
node
);
private
checkTextLength
(
text
:
string
)
{
if
(
text
.
length
>=
4000
)
{
throw
new
Error
(
"消息不能超过4000个字"
);
}
}
return
sendingNodes
;
}
private
handleSaveRange
()
{
const
sel
=
window
.
getSelection
();
if
(
sel
&&
sel
.
rangeCount
)
{
const
range
=
sel
.
getRangeAt
(
0
);
const
oldRange
=
this
.
getRange
&&
this
.
getRange
();
if
(
this
.
messageInputBox
&&
this
.
messageInputBox
.
contains
(
range
.
endContainer
)
)
{
if
(
oldRange
&&
range
.
collapsed
&&
range
.
endContainer
===
oldRange
.
endContainer
&&
range
.
endOffset
===
oldRange
.
endOffset
)
{
return
;
}
private
checkTextLength
(
text
:
string
)
{
if
(
text
.
length
>=
4000
)
{
throw
new
Error
(
"消息不能超过4000个字"
);
}
}
private
handleSaveRange
()
{
const
sel
=
window
.
getSelection
();
if
(
sel
&&
sel
.
rangeCount
)
{
const
range
=
sel
.
getRangeAt
(
0
);
const
oldRange
=
this
.
getRange
&&
this
.
getRange
();
if
(
this
.
messageInputBox
&&
this
.
messageInputBox
.
contains
(
range
.
endContainer
)
)
{
if
(
oldRange
&&
range
.
collapsed
&&
range
.
endContainer
===
oldRange
.
endContainer
&&
range
.
endOffset
===
oldRange
.
endOffset
)
{
return
;
this
.
saveRange
&&
this
.
saveRange
(
range
);
}
}
this
.
saveRange
&&
this
.
saveRange
(
range
);
}
}
}
private
saveRange
?:
(
param
:
Range
)
=>
void
;
private
getRange
?:
()
=>
Range
|
null
;
/**
* 在光标处插入元素
*/
public
insertHtmlAtCaret
=
(
function
()
{
let
range
:
Range
|
null
=
null
;
return
function
(
this
:
Input
,
html
:
string
)
{
if
(
this
.
saveRange
==
null
)
{
this
.
saveRange
=
(
alternate
)
=>
(
range
=
alternate
);
}
if
(
this
.
getRange
==
null
)
{
this
.
getRange
=
()
=>
range
;
}
const
sel
=
window
.
getSelection
();
if
(
sel
)
{
if
(
range
)
{
range
.
deleteContents
();
sel
.
removeAllRanges
();
sel
.
addRange
(
range
);
}
else
{
this
.
focus
();
range
=
sel
.
getRangeAt
(
0
);
document
.
execCommand
(
"selectAll"
);
window
.
getSelection
()?.
collapseToEnd
();
}
}
const
el
=
document
.
createElement
(
"div"
);
el
.
innerHTML
=
html
;
const
frag
=
document
.
createDocumentFragment
();
let
node
=
null
;
let
lastNode
=
null
;
while
((
node
=
el
.
firstChild
))
{
lastNode
=
frag
.
appendChild
(
node
);
}
range
?.
insertNode
(
frag
);
// Preserve the selection
if
(
lastNode
)
{
if
(
range
)
{
range
=
range
.
cloneRange
();
range
.
setStartAfter
(
lastNode
);
range
.
collapse
(
true
);
sel
?.
removeAllRanges
();
sel
?.
addRange
(
range
);
}
}
};
})();
private
selectEmoji
(
emoji
:
any
)
{
this
.
insertHtmlAtCaret
(
emoji
.
emoji_chars
);
this
.
hideEmoji
();
}
private
isImage
(
file
:
File
)
{
return
file
.
type
.
startsWith
(
"image"
);
}
private
buildImageHtml
(
file
:
File
)
{
const
url
=
URL
.
createObjectURL
(
file
);
return
`<img class="
${
IMAGE_INFO_CLASS
}
" tabindex="-1" src="
${
url
}
" data-image='
${
JSON
.
stringify
(
{
url
,
name
:
file
.
name
,
size
:
file
.
size
,
}
)}
'>`
;
}
private
buildFileHtml
(
file
:
File
)
{
const
extension
=
file
.
name
.
split
(
"."
);
const
type
=
getFileType
(
file
.
name
);
return
`<div class="
${
FILE_INFO_CLASS
}
" tabindex="-1" title="
${
extension
[
extension
.
length
-
1
]
}
" data-file='
${
JSON
.
stringify
({
url
:
URL
.
createObjectURL
(
file
),
name
:
file
.
name
,
size
:
file
.
size
,
})}
'><div style="display: inline-block"><div class="file-name text-truncate text-nowrap">
${
file
.
name
}
</div><div class="file-size">
${
formatFileSize
(
file
.
size
)}
</div></div><span class="file-icon" title="
${
type
}
">
${
getSvg
(
type
)}
</span></div> `
;
// 注意
<
/div>最后面有个空
白
}
private
onChange
(
e
:
Event
)
{
const
target
=
e
.
target
as
HTMLElement
;
if
(
target
)
{
const
input
=
document
.
getElementById
(
target
.
id
as
string
)
as
HTMLInputElement
;
if
(
input
)
{
const
files
=
input
.
files
;
if
(
files
&&
files
.
length
)
{
let
html
=
""
;
for
(
let
index
=
0
;
index
<
files
.
length
;
index
++
)
{
const
file
=
files
[
index
];
if
(
file
.
size
<=
0
)
{
this
.
$emit
(
"error"
,
MESSAGE_FILE_EMPTY
);
return
;
private
saveRange
?:
(
param
:
Range
)
=>
void
;
private
getRange
?:
()
=>
Range
|
null
;
/**
* 在光标处插入元素
*/
public
insertHtmlAtCaret
=
(
function
()
{
let
range
:
Range
|
null
=
null
;
return
function
(
this
:
Input
,
html
:
string
)
{
if
(
this
.
saveRange
==
null
)
{
this
.
saveRange
=
(
alternate
)
=>
(
range
=
alternate
);
}
if
(
this
.
isImage
(
file
))
{
if
(
file
.
size
>=
MAX_IMAGE_SIZE
)
{
this
.
$emit
(
"error"
,
MESSAGE_IMAGE_TOO_LARGE
);
return
;
}
html
+=
this
.
buildImageHtml
(
file
);
}
else
{
if
(
file
.
size
>=
MAX_FILE_SIZE
)
{
this
.
$emit
(
"error"
,
MESSAGE_FILE_TOO_LARGE
);
return
;
}
html
+=
this
.
buildFileHtml
(
file
);
if
(
this
.
getRange
==
null
)
{
this
.
getRange
=
()
=>
range
;
}
const
sel
=
window
.
getSelection
();
if
(
sel
)
{
if
(
range
)
{
range
.
deleteContents
();
sel
.
removeAllRanges
();
sel
.
addRange
(
range
);
}
else
{
this
.
focus
();
range
=
sel
.
getRangeAt
(
0
);
document
.
execCommand
(
"selectAll"
);
window
.
getSelection
()?.
collapseToEnd
();
}
}
}
this
.
insertHtmlAtCaret
(
html
);
const
el
=
document
.
createElement
(
"div"
);
el
.
innerHTML
=
html
;
const
frag
=
document
.
createDocumentFragment
();
let
node
=
null
;
let
lastNode
=
null
;
while
((
node
=
el
.
firstChild
))
{
lastNode
=
frag
.
appendChild
(
node
);
}
range
?.
insertNode
(
frag
);
// Preserve the selection
if
(
lastNode
)
{
if
(
range
)
{
range
=
range
.
cloneRange
();
range
.
setStartAfter
(
lastNode
);
range
.
collapse
(
true
);
sel
?.
removeAllRanges
();
sel
?.
addRange
(
range
);
}
}
};
})();
private
selectEmoji
(
emoji
:
any
)
{
this
.
insertHtmlAtCaret
(
emoji
.
emoji_chars
);
this
.
hideEmoji
();
}
private
isImage
(
file
:
File
)
{
return
file
.
type
.
startsWith
(
"image"
);
}
private
buildImageHtml
(
file
:
File
)
{
const
url
=
URL
.
createObjectURL
(
file
);
return
`<img class="
${
IMAGE_INFO_CLASS
}
" tabindex="-1" src="
${
url
}
" data-image='
${
JSON
.
stringify
(
{
url
,
name
:
file
.
name
,
size
:
file
.
size
,
}
)}
'>`
;
}
private
buildFileHtml
(
file
:
File
)
{
const
extension
=
file
.
name
.
split
(
"."
);
const
type
=
getFileType
(
file
.
name
);
return
`<div class="
${
FILE_INFO_CLASS
}
" tabindex="-1" title="
${
extension
[
extension
.
length
-
1
]
}
" data-file='
${
JSON
.
stringify
({
url
:
URL
.
createObjectURL
(
file
),
name
:
file
.
name
,
size
:
file
.
size
,
})}
'><div style="display: inline-block"><div class="file-name text-truncate text-nowrap">
${
file
.
name
}
</div><div class="file-size">
${
formatFileSize
(
file
.
size
)}
</div></div><span class="file-icon" title="
${
type
}
">
${
getSvg
(
type
)}
</span></div> `
;
// 注意
<
/div>最后面有个空
白
}
private
onChange
(
e
:
Event
)
{
const
target
=
e
.
target
as
HTMLElement
;
if
(
target
)
{
const
input
=
document
.
getElementById
(
target
.
id
as
string
)
as
HTMLInputElement
;
if
(
input
)
{
const
files
=
input
.
files
;
if
(
files
&&
files
.
length
)
{
let
html
=
""
;
for
(
let
index
=
0
;
index
<
files
.
length
;
index
++
)
{
const
file
=
files
[
index
];
if
(
file
.
size
<=
0
)
{
this
.
$emit
(
"error"
,
MESSAGE_FILE_EMPTY
);
return
;
}
if
(
this
.
isImage
(
file
))
{
if
(
file
.
size
>=
MAX_IMAGE_SIZE
)
{
this
.
$emit
(
"error"
,
MESSAGE_IMAGE_TOO_LARGE
);
return
;
}
html
+=
this
.
buildImageHtml
(
file
);
}
else
{
if
(
file
.
size
>=
MAX_FILE_SIZE
)
{
this
.
$emit
(
"error"
,
MESSAGE_FILE_TOO_LARGE
);
return
;
}
html
+=
this
.
buildFileHtml
(
file
);
}
}
this
.
insertHtmlAtCaret
(
html
);
}
}
}
}
}
}
private
toggleEmoji
()
{
this
.
emojiPanelVisibility
=
!
this
.
emojiPanelVisibility
;
}
private
hideEmoji
(
e
?:
Event
)
{
if
(
e
&&
e
.
target
)
{
const
target
=
e
.
target
as
HTMLElement
;
if
(
target
.
closest
(
".emoji-picker"
))
{
return
;
}
private
toggleEmoji
()
{
this
.
emojiPanelVisibility
=
!
this
.
emojiPanelVisibility
;
}
this
.
emojiPanelVisibility
=
false
;
}
private
setupEmoji
()
{
EmojiService
.
onReady
(()
=>
{
const
service
=
new
EmojiService
();
service
.
getEmoji
().
then
((
r
)
=>
{
if
(
r
)
{
this
.
emoji
=
r
.
list
;
private
hideEmoji
(
e
?:
Event
)
{
if
(
e
&&
e
.
target
)
{
const
target
=
e
.
target
as
HTMLElement
;
if
(
target
.
closest
(
".emoji-picker"
))
{
return
;
}
}
});
});
}
this
.
emojiPanelVisibility
=
false
;
}
private
setupEmoji
()
{
EmojiService
.
onReady
(()
=>
{
const
service
=
new
EmojiService
();
service
.
getEmoji
().
then
((
r
)
=>
{
if
(
r
)
{
this
.
emoji
=
r
.
list
;
}
});
});
}
}
</
script
>
<
style
lang=
"less"
scoped
>
.input-wrap
{
position
:
relative
;
padding-left
:
20px
;
position
:
relative
;
padding-left
:
20px
;
/deep/.input-el-scrollbar.el-scrollbar
{
height
:
calc
(
100%
-
35px
);
/deep/.input-el-scrollbar.el-scrollbar
{
height
:
calc
(
100%
-
35px
);
>
.el-scrollbar__wrap
{
overflow-x
:
hidden
;
}
>
.el-scrollbar__wrap
>
.el-scrollbar__view
{
min-height
:
100%
;
display
:
flex
;
>
.el-scrollbar__wrap
{
overflow-x
:
hidden
;
}
//
输入框
>
.input-container
{
width
:
100%
;
font-size
:
14px
;
padding
:
10px
20px
10px
0
;
outline
:
0
;
white-space
:
pre-wrap
;
user-select
:
text
;
>
.el-scrollbar__wrap
>
.el-scrollbar__view
{
min-height
:
100%
;
display
:
flex
;
//
输入框
>
.input-container
{
width
:
100%
;
font-size
:
14px
;
padding
:
10px
20px
10px
0
;
outline
:
0
;
white-space
:
pre-wrap
;
user-select
:
text
;
&::selection
{
background-color
:
#cce6fc
;
}
&::selection
{
background-color
:
#cce6fc
;
}
img
{
max-width
:
300px
;
font-size
:
50px
;
vertical-align
:
bottom
;
border
:
1px
solid
transparent
;
img
{
max-width
:
300px
;
font-size
:
50px
;
vertical-align
:
bottom
;
border
:
1px
solid
transparent
;
&:focus
{
border-color
:
var
(
--main-color
);
}
&:focus
{
border-color
:
var
(
--main-color
);
}
&
::selection
{
background-color
:
#cce6fc
;
}
}
&
::selection
{
background-color
:
#cce6fc
;
}
}
.file-info
{
padding
:
10px
;
border
:
1px
solid
#c5d4e5
;
border-radius
:
4px
;
display
:
inline-block
;
margin-right
:
10px
;
-webkit-user-modify
:
read-only
;
user-select
:
none
;
.file-name
{
color
:
#000
;
font-size
:
14px
;
max-width
:
130px
;
}
.file-info
{
padding
:
10px
;
border
:
1px
solid
#c5d4e5
;
border-radius
:
4px
;
display
:
inline-block
;
margin-right
:
10px
;
-webkit-user-modify
:
read-only
;
user-select
:
none
;
.file-name
{
color
:
#000
;
font-size
:
14px
;
max-width
:
130px
;
}
.file-size
{
margin-top
:
10px
;
}
.file-size
{
margin-top
:
10px
;
}
}
}
}
}
}
}
.input-emoji
{
position
:
absolute
;
left
:
0
;
top
:
-225px
;
outline
:
0
;
}
.input-emoji
{
position
:
absolute
;
left
:
0
;
top
:
-225px
;
outline
:
0
;
}
}
.tool-bar
{
padding-top
:
10px
;
user-select
:
none
;
.tool-bar-icon
{
height
:
16px
;
width
:
16px
;
cursor
:
pointer
;
}
.offset
{
margin
:
0
22px
;
}
padding-top
:
10px
;
user-select
:
none
;
.tool-bar-icon
{
height
:
16px
;
width
:
16px
;
cursor
:
pointer
;
}
.offset
{
margin
:
0
22px
;
}
}
.emoji-picker
{
position
:
absolute
;
z-index
:
2
;
bottom
:
99px
;
left
:
-1px
;
background-color
:
#fff
;
padding
:
20px
;
padding-right
:
0
;
padding-bottom
:
10px
;
border
:
1px
solid
#f0f0f0
;
right
:
45px
;
overflow
:
hidden
;
.el-scrollbar
{
height
:
200px
;
}
.emoji-item
{
display
:
inline-flex
;
cursor
:
pointer
;
min-width
:
35px
;
min-height
:
25px
;
font-size
:
20px
;
vertical-align
:
top
;
margin-top
:
5px
;
justify-content
:
center
;
align-items
:
center
;
}
position
:
absolute
;
z-index
:
2
;
bottom
:
99px
;
left
:
-1px
;
background-color
:
#fff
;
padding
:
20px
;
padding-right
:
0
;
padding-bottom
:
10px
;
border
:
1px
solid
#f0f0f0
;
right
:
45px
;
overflow
:
hidden
;
.el-scrollbar
{
height
:
200px
;
}
.emoji-item
{
display
:
inline-flex
;
cursor
:
pointer
;
min-width
:
35px
;
min-height
:
25px
;
font-size
:
20px
;
vertical-align
:
top
;
margin-top
:
5px
;
justify-content
:
center
;
align-items
:
center
;
}
}
#chat-upload-file
{
visibility
:
hidden
;
position
:
absolute
;
visibility
:
hidden
;
position
:
absolute
;
}
</
style
>
store/model.ts
View file @
452addc8
...
...
@@ -15,56 +15,57 @@ export namespace ChatStore {
export
const
ns
=
"chatStore"
;
/* state */
export
const
STATE_CHAT_DIALOG_VISIBLE
=
"会话模块弹窗显示状态"
;
export
type
STATE_CHAT_DIALOG_VISIBLE
=
boolean
export
type
STATE_CHAT_DIALOG_VISIBLE
=
boolean
;
export
const
STATE_CHAT_DIALOG_IS_SINGLE
=
"会话模块是否是单个弹窗"
;
export
type
STATE_CHAT_DIALOG_IS_SINGLE
=
boolean
export
type
STATE_CHAT_DIALOG_IS_SINGLE
=
boolean
;
export
const
STATE_MY_CHAT_ROOM_LIST
=
"我的会话列表"
;
export
type
STATE_MY_CHAT_ROOM_LIST
=
{
list
:
ChatType
[];
total
:
number
;
}
|
null
}
|
null
;
export
const
STATE_SINGLE_CHAT
=
"单独的会话"
;
export
type
STATE_SINGLE_CHAT
=
ChatType
|
null
export
type
STATE_SINGLE_CHAT
=
ChatType
|
null
;
export
const
STATE_CHAT_MSG_HISTORY
=
"某个会话聊天记录"
;
export
type
STATE_CHAT_MSG_HISTORY
=
dto
.
MessageRequestResult
|
null
export
type
STATE_CHAT_MSG_HISTORY
=
dto
.
MessageRequestResult
|
null
;
export
const
STATE_CHAT_SENDING_MESSAGES
=
"sendingMessages"
;
export
type
STATE_CHAT_SENDING_MESSAGES
=
dto
.
MessageRequestResult
|
null
export
type
STATE_CHAT_SENDING_MESSAGE
=
dto
.
Message
export
type
STATE_CHAT_SENDING_MESSAGES
=
dto
.
MessageRequestResult
|
null
;
export
type
STATE_CHAT_SENDING_MESSAGE
=
dto
.
Message
;
export
const
STATE_CHAT_CURRENT_CHAT_ID
=
"当前chat-id"
;
export
type
STATE_CHAT_CURRENT_CHAT_ID
=
number
|
null
export
type
STATE_CHAT_CURRENT_CHAT_ID
=
number
|
null
;
export
const
STATE_CHAT_CURRENT_CHAT_VERSION
=
"当前chat的Uniplat version"
;
export
type
STATE_CHAT_CURRENT_CHAT_VERSION
=
number
|
null
export
type
STATE_CHAT_CURRENT_CHAT_VERSION
=
number
|
null
;
export
const
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
=
"是否是当前chat的成员"
;
export
type
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
=
boolean
export
type
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
=
boolean
;
export
const
STATE_CHAT_CURRENT_USER_UID
=
"用户的UiplatId"
;
export
type
STATE_CHAT_CURRENT_USER_UID
=
number
|
null
export
const
STATE_CHAT_CURRENT_USER_TYPE
=
"当前用户类型状态,25-普通用户,92-客服"
;
export
type
STATE_CHAT_CURRENT_USER_TYPE
=
string
|
null
export
type
STATE_CHAT_CURRENT_USER_UID
=
number
|
null
;
export
const
STATE_CHAT_CURRENT_USER_TYPE
=
"当前用户类型状态,25-普通用户,92-客服"
;
export
type
STATE_CHAT_CURRENT_USER_TYPE
=
string
|
null
;
export
const
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
=
"当前chat的Uniplat id"
;
export
type
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
=
string
|
null
export
type
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
=
string
|
null
;
export
const
STATE_CHAT_USERNAME
=
"会话用户id-name"
;
export
type
STATE_CHAT_USERNAME
=
{
[
key
:
string
]:
string
}
|
{}
export
type
STATE_CHAT_USERNAME
=
{
[
key
:
string
]:
string
}
|
{};
export
const
STATE_CHAT_MY_ID
=
"聊天窗口显示在右边那个人的id"
;
export
type
STATE_CHAT_MY_ID
=
string
|
null
export
type
STATE_CHAT_MY_ID
=
string
|
null
;
export
const
STATE_CHAT_MY_UID
=
"聊天窗口显示在右边那个人的uid"
;
export
type
STATE_CHAT_MY_UID
=
string
|
null
export
type
STATE_CHAT_MY_UID
=
string
|
null
;
export
const
STATE_CHAT_SOURCE
=
"stateChatSource"
;
export
type
STATE_CHAT_SOURCE
=
StateChatSourceDirection
export
type
STATE_CHAT_SOURCE
=
StateChatSourceDirection
;
export
const
STATE_CURRENT_CHAT_INPUTING
=
"当前会话正在输入的人"
;
export
type
STATE_CURRENT_CHAT_INPUTING
=
string
[]
export
type
STATE_CURRENT_CHAT_INPUTING
=
string
[]
;
export
const
STATE_CURRENT_CHAT_INITING
=
"当前会是否正在初始化"
;
export
type
STATE_CURRENT_CHAT_INITING
=
boolean
export
type
STATE_CURRENT_CHAT_INITING
=
boolean
;
export
const
STATE_CHAT_SEND_FAIL_MESSAGE
=
"最新一条发送失败消息"
;
export
type
STATE_CHAT_SEND_FAIL_MESSAGE
=
string
|
null
export
type
STATE_CHAT_SEND_FAIL_MESSAGE
=
string
|
null
;
/**
* 消息来源,是来自客服端(Server),还是来自于顾客端(Client)
...
...
@@ -77,184 +78,198 @@ export namespace ChatStore {
export
const
STATE_CURRENT_CHAT_MEMBERS
=
"当前会话参与者"
;
export
type
STATE_CURRENT_CHAT_MEMBERS
=
|
readonly
(
dto
.
ChatMember
&
dto
.
ChatMemberExtraInfo
)[]
|
null
|
null
;
export
const
STATE_CURRENT_CHAT_TITLE
=
"会话标题"
;
export
type
STATE_CURRENT_CHAT_TITLE
=
string
export
type
STATE_CURRENT_CHAT_TITLE
=
string
;
export
const
STATE_FUNC_SCROLL_TO_BOTTOM
=
"收到消息后滚动到底部的方法"
;
export
type
STATE_FUNC_SCROLL_TO_BOTTOM
=
()
=>
void
export
type
STATE_FUNC_SCROLL_TO_BOTTOM
=
()
=>
void
;
export
const
STATE_FUNC_ON_NEW_MSG
=
"收到消息回调方法"
;
export
type
STATE_FUNC_ON_NEW_MSG
=
(
e
:
chatDto
.
Message
)
=>
void
export
type
STATE_FUNC_ON_NEW_MSG
=
(
e
:
chatDto
.
Message
)
=>
void
;
/* getter */
export
const
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
=
"当前会话未退出的参与者"
;
export
type
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
=
dto
.
ChatMembers
|
null
export
type
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
=
dto
.
ChatMembers
|
null
;
export
const
GETTER_CURRENT_CURRENT_CHAT
=
"当前打开的会话"
;
export
type
GETTER_CURRENT_CURRENT_CHAT
=
ChatType
|
null
export
type
GETTER_CURRENT_CURRENT_CHAT
=
ChatType
|
null
;
/* mutation */
export
const
MUTATION_SHOW_CHAT
=
"打开会话弹窗"
;
export
type
MUTATION_SHOW_CHAT
=
()
=>
void
export
type
MUTATION_SHOW_CHAT
=
()
=>
void
;
export
const
MUTATION_HIDE_CHAT
=
"关闭会话弹窗"
;
export
type
MUTATION_HIDE_CHAT
=
()
=>
void
export
type
MUTATION_HIDE_CHAT
=
()
=>
void
;
export
const
MUTATION_SAVE_CHAT_LIST
=
"保存我的会话列表"
;
export
type
MUTATION_SAVE_CHAT_LIST
=
(
list
:
STATE_MY_CHAT_ROOM_LIST
)
=>
void
)
=>
void
;
export
const
MUTATION_PUSH_CHAT_MSG_HISTORY
=
"新增下一页聊天记录"
;
export
type
MUTATION_PUSH_CHAT_MSG_HISTORY
=
(
list
:
ChatStore
.
STATE_CHAT_MSG_HISTORY
)
=>
void
)
=>
void
;
export
const
MUTATION_INITING_CHAT
=
"当前会话正在初始化"
;
export
type
MUTATION_INITING_CHAT
=
()
=>
void
export
type
MUTATION_INITING_CHAT
=
()
=>
void
;
export
const
MUTATION_INITING_CHAT_DONE
=
"当前会话初始完成"
;
export
type
MUTATION_INITING_CHAT_DONE
=
()
=>
void
export
type
MUTATION_INITING_CHAT_DONE
=
()
=>
void
;
export
const
MUTATION_UNSHIFT_CHAT_MSG_HISTORY
=
"新增上一页聊天记录"
;
export
type
MUTATION_UNSHIFT_CHAT_MSG_HISTORY
=
(
list
:
ChatStore
.
STATE_CHAT_MSG_HISTORY
)
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_CHAT_MSG_HISTORY
=
"清空聊天记录"
;
export
type
MUTATION_CLEAR_CHAT_MSG_HISTORY
=
()
=>
void
export
type
MUTATION_CLEAR_CHAT_MSG_HISTORY
=
()
=>
void
;
export
const
MUTATION_SAVE_SINGLE_CHAT
=
"设置单独的会话"
;
export
type
MUTATION_SAVE_SINGLE_CHAT
=
(
v
:
ChatType
)
=>
void
export
type
MUTATION_SAVE_SINGLE_CHAT
=
(
v
:
ChatType
)
=>
void
;
export
const
MUTATION_CLEAR_SINGLE_CHAT
=
"清空单独的会话"
;
export
type
MUTATION_CLEAR_SINGLE_CHAT
=
()
=>
void
export
type
MUTATION_CLEAR_SINGLE_CHAT
=
()
=>
void
;
export
const
MUTATION_SAVE_SEND_FAIL_MESSAGE
=
"更新最新一条发送失败消息"
;
export
type
MUTATION_SAVE_SEND_FAIL_MESSAGE
=
(
param
:
{
msg
:
string
,
ts
:
number
})
=>
void
export
type
MUTATION_SAVE_SEND_FAIL_MESSAGE
=
(
param
:
{
msg
:
string
;
ts
:
number
;
})
=>
void
;
export
const
MUTATION_SAVE_USERNAME
=
"更新用户id-name"
;
export
type
MUTATION_SAVE_USERNAME
=
(
param
:
{
id
:
string
,
name
:
string
})
=>
void
export
type
MUTATION_SAVE_USERNAME
=
(
param
:
{
id
:
string
;
name
:
string
;
})
=>
void
;
export
const
MUTATION_SAVE_CURRENT_CHAT_ID
=
"保存当前chat-id"
;
export
type
MUTATION_SAVE_CURRENT_CHAT_ID
=
(
chatId
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_CURRENT_CHAT_ID
=
"清空chat-id"
;
export
type
MUTATION_CLEAR_CURRENT_CHAT_ID
=
()
=>
void
export
type
MUTATION_CLEAR_CURRENT_CHAT_ID
=
()
=>
void
;
export
const
MUTATION_SET_CURRENT_USER_UID
=
"设置当前用户UniplatId"
;
export
type
MUTATION_SET_CURRENT_USER_UID
=
(
v
:
number
)
=>
void
export
type
MUTATION_SET_CURRENT_USER_UID
=
(
v
:
number
)
=>
void
;
export
const
MUTATION_SAVE_CURRENT_CHAT_VERSION
=
"保存当前chat uniplat version"
;
export
type
MUTATION_SAVE_CURRENT_CHAT_VERSION
=
(
v
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_VERSION
)
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_CURRENT_CHAT_VERSION
=
"清空chat uniplat version"
;
export
type
MUTATION_CLEAR_CURRENT_CHAT_VERSION
=
()
=>
void
export
type
MUTATION_CLEAR_CURRENT_CHAT_VERSION
=
()
=>
void
;
export
const
MUTATION_SAVE_CURRENT_CHAT_UNIPLAT_ID
=
"保存当前chat uniplat id"
;
export
type
MUTATION_SAVE_CURRENT_CHAT_UNIPLAT_ID
=
(
v
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
)
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_CURRENT_CHAT_UNIPLAT_ID
=
"清空chat uniplat id"
;
export
type
MUTATION_CLEAR_CURRENT_CHAT_UNIPLAT_ID
=
()
=>
void
export
type
MUTATION_CLEAR_CURRENT_CHAT_UNIPLAT_ID
=
()
=>
void
;
export
const
MUTATION_SAVE_MYSELF_ID
=
"保存我的id:聊天窗口显示在右边那个人的id"
;
export
type
MUTATION_SAVE_MYSELF_ID
=
()
=>
void
export
type
MUTATION_SAVE_MYSELF_ID
=
()
=>
void
;
export
const
MUTATION_CLEAR_MYSELF_ID
=
"清空我的id:聊天窗口显示在右边那个人的id"
;
export
type
MUTATION_CLEAR_MYSELF_ID
=
()
=>
void
export
type
MUTATION_CLEAR_MYSELF_ID
=
()
=>
void
;
export
const
MUTATION_SET_CHAT_SOURCE
=
"setChatSource"
;
export
type
MUTATION_SET_CHAT_SOURCE
=
(
payload
:
StateChatSourceDirection
)
=>
void
)
=>
void
;
export
const
MUTATION_SAVE_CURRENT_CHAT_MEMBERS
=
"保存当前会话参与者"
;
export
type
MUTATION_SAVE_CURRENT_CHAT_MEMBERS
=
(
params
:
ChatStore
.
STATE_CURRENT_CHAT_MEMBERS
)
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_CURRENT_CHAT_MEMBERS
=
"清空当前会话参与者"
;
export
type
MUTATION_CLEAR_CURRENT_CHAT_MEMBERS
=
()
=>
void
export
type
MUTATION_CLEAR_CURRENT_CHAT_MEMBERS
=
()
=>
void
;
export
const
MUTATION_SAVE_CHAT_TITLE
=
"保存会话标题"
;
export
type
MUTATION_SAVE_CHAT_TITLE
=
(
title
:
ChatStore
.
STATE_CURRENT_CHAT_TITLE
)
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_CHAT_TITLE
=
"清空会话标题"
;
export
type
MUTATION_CLEAR_CHAT_TITLE
=
()
=>
void
export
type
MUTATION_CLEAR_CHAT_TITLE
=
()
=>
void
;
export
const
MUTATION_SCROLL_TO_BOTTOM
=
"收到新消息后滚动到底部"
;
export
type
MUTATION_SCROLL_TO_BOTTOM
=
()
=>
void
export
type
MUTATION_SCROLL_TO_BOTTOM
=
()
=>
void
;
export
const
MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM
=
"保存收到新消息后滚动到底部的方法"
;
export
type
MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM
=
(
func
:
()
=>
void
)
=>
void
export
type
MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM
=
(
func
:
()
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM
=
"删除收到新消息后滚动到底部的方法"
;
export
type
MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM
=
()
=>
void
export
type
MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM
=
()
=>
void
;
export
const
MUTATION_SAVE_FUNC_ON_NEW_MSG
=
"保存收到新消息后的方法"
;
export
type
MUTATION_SAVE_FUNC_ON_NEW_MSG
=
(
func
:
(
e
:
dto
.
Message
)
=>
void
)
=>
void
export
type
MUTATION_SAVE_FUNC_ON_NEW_MSG
=
(
func
:
(
e
:
dto
.
Message
)
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_FUNC_ON_NEW_MSG
=
"删除收到新消息后的方法"
;
export
type
MUTATION_CLEAR_FUNC_ON_NEW_MSG
=
()
=>
void
export
type
MUTATION_CLEAR_FUNC_ON_NEW_MSG
=
()
=>
void
;
export
const
MUTATION_APPEND_SENDING_MESSAGE
=
"appendSendingMessage"
;
export
type
MUTATION_APPEND_SENDING_MESSAGE
=
(
payload
:
dto
.
Message
)
=>
void
export
type
MUTATION_APPEND_SENDING_MESSAGE
=
(
payload
:
dto
.
Message
)
=>
void
;
export
const
MUTATION_REMOVE_SENDING_MESSAGE
=
"removeSendingMessage"
;
export
type
MUTATION_REMOVE_SENDING_MESSAGE
=
(
id
:
number
)
=>
void
export
type
MUTATION_REMOVE_SENDING_MESSAGE
=
(
id
:
number
)
=>
void
;
export
const
MUTATION_FAILED_SENDING_MESSAGE
=
"failedSendingMessage"
;
export
type
MUTATION_FAILED_SENDING_MESSAGE
=
(
id
:
number
)
=>
void
export
type
MUTATION_FAILED_SENDING_MESSAGE
=
(
id
:
number
)
=>
void
;
export
const
MUTATION_SAVE_CURRENT_CHAT_INPUTING
=
"保存正在输入"
;
export
type
MUTATION_SAVE_CURRENT_CHAT_INPUTING
=
(
params
:
chatDto
.
NotifyMessage
)
=>
void
)
=>
void
;
export
const
MUTATION_CLEAR_CURRENT_CHAT_INPUTING
=
"清空正在输入"
;
export
type
MUTATION_CLEAR_CURRENT_CHAT_INPUTING
=
()
=>
void
export
type
MUTATION_CLEAR_CURRENT_CHAT_INPUTING
=
()
=>
void
;
export
const
MUTATION_CHAT_UPDATE_IS_MEMBER
=
"更新是否是当前成员"
;
export
type
MUTATION_CHAT_UPDATE_IS_MEMBER
=
(
v
:
boolean
)
=>
Promise
<
void
>
export
type
MUTATION_CHAT_UPDATE_IS_MEMBER
=
(
v
:
boolean
)
=>
Promise
<
void
>
;
export
const
MUTATION_CHAT_UPDATE_USER_TYPE
=
"更新当前用户的类型"
;
export
type
MUTATION_CHAT_UPDATE_USER_TYPE
=
(
v
:
string
)
=>
Promise
<
void
>
export
type
MUTATION_CHAT_UPDATE_USER_TYPE
=
(
v
:
string
)
=>
Promise
<
void
>
;
/* action */
export
const
ACTION_GET_MY_CHAT_LIST
=
"获取我的会话列表"
;
export
type
ACTION_GET_MY_CHAT_LIST
=
(
keyword
?:
string
)
=>
void
export
type
ACTION_GET_MY_CHAT_LIST
=
(
keyword
?:
string
)
=>
Promise
<
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
>
;
export
const
ACTION_JOIN_CHAT
=
"加入某个会话"
;
export
type
ACTION_JOIN_CHAT
=
(
chatId
:
number
)
=>
void
export
type
ACTION_JOIN_CHAT
=
(
chatId
:
number
)
=>
void
;
export
const
ACTION_GET_CHAT_MESSAGES
=
"打开某个会话时获取他的聊天记录"
;
export
type
ACTION_GET_CHAT_MESSAGES
=
()
=>
Promise
<
dto
.
MessageRequestResult
>
()
=>
Promise
<
dto
.
MessageRequestResult
>
;
export
const
ACTION_GET_CHAT_MESSAGES_BEFORE_SPECIFIC_ID
=
"获取某个消息之前的10条消息"
;
export
type
ACTION_GET_CHAT_MESSAGES_BEFORE_SPECIFIC_ID
=
(
msgId
:
number
)
=>
Promise
<
dto
.
MessageRequestResult
>
)
=>
Promise
<
dto
.
MessageRequestResult
>
;
export
const
ACTION_GET_CHAT_MESSAGES_AFTER_SPECIFIC_ID
=
"获取某个消息之后的10条消息"
;
export
type
ACTION_GET_CHAT_MESSAGES_AFTER_SPECIFIC_ID
=
(
msgId
:
number
)
=>
Promise
<
dto
.
MessageRequestResult
>
)
=>
Promise
<
dto
.
MessageRequestResult
>
;
export
const
ACTION_GET_FRESH_MESSAGE
=
"获取最新的消息"
;
export
type
ACTION_GET_FRESH_MESSAGE
=
()
=>
Promise
<
dto
.
MessageRequestResult
>
()
=>
Promise
<
dto
.
MessageRequestResult
>
;
export
const
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
=
"客服向顾客发起新会话"
;
export
type
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
=
(
params
:
{
...
...
@@ -262,63 +277,65 @@ export namespace ChatStore {
selectedListId
:
string
;
uids
:
string
[];
showByPage
?:
boolean
;
})
=>
Promise
<
void
>
})
=>
Promise
<
void
>
;
export
const
ACTION_CREATE_NEW_CHAT_BY_CLIENT
=
"顾客向客服发起新会话"
;
export
type
ACTION_CREATE_NEW_CHAT_BY_CLIENT
=
(
params
:
{
modelName
:
string
;
selectedListId
:
string
;
uids
:
string
[];
})
=>
Promise
<
void
>
})
=>
Promise
<
void
>
;
export
const
ACTION_CREATE_NEW_CHAT_BY_CLIENT_SIDE
=
"startNewConversationByCustomerSide"
;
export
type
ACTION_CREATE_NEW_CHAT_BY_CLIENT_SIDE
=
(
option
:
{
customerServiceId
?:
number
|
string
;
customerServiceGroupId
?:
number
|
string
;
})
=>
Promise
<
number
>
})
=>
Promise
<
number
>
;
export
const
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
=
"action:保存当前chat-id"
;
export
type
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
=
(
chatId
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
=>
Promise
<
void
>
export
type
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
=
(
chatId
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
=>
Promise
<
void
>
;
export
const
ACTION_CLEAR_CURRENT_CHAT_DATA
=
"action:清空当前会话数据"
;
export
type
ACTION_CLEAR_CURRENT_CHAT_DATA
=
()
=>
Promise
<
void
>
export
type
ACTION_CLEAR_CURRENT_CHAT_DATA
=
()
=>
Promise
<
void
>
;
export
const
ACTION_REGISTER_EVENT
=
"给当前会话注册事件通知"
;
export
type
ACTION_REGISTER_EVENT
=
()
=>
Promise
<
void
>
export
type
ACTION_REGISTER_EVENT
=
()
=>
Promise
<
void
>
;
export
const
ACTION_GET_CHAT_MEMBERS
=
"获取会话成员列表"
;
export
type
ACTION_GET_CHAT_MEMBERS
=
()
=>
Promise
<
void
>
export
type
ACTION_GET_CHAT_MEMBERS
=
()
=>
Promise
<
void
>
;
export
const
ACTION_SEND_MESSAGE
=
"发送消息"
;
export
type
ACTION_SEND_MESSAGE
=
(
params
:
{
msgType
:
"text"
|
"image"
|
"file"
|
"voice"
|
"video"
;
msg
:
string
;
ts
?:
number
;
})
=>
void
})
=>
void
;
export
const
ACTION_TERINATE_CHAT
=
"结束会话"
;
export
type
ACTION_TERINATE_CHAT
=
()
=>
Promise
<
void
>
export
type
ACTION_TERINATE_CHAT
=
()
=>
Promise
<
void
>
;
export
const
ACTION_CHAT_ADD_MEMBERS
=
"添加成员"
;
export
type
ACTION_CHAT_ADD_MEMBERS
=
(
uids
:
string
[])
=>
Promise
<
void
>
export
type
ACTION_CHAT_ADD_MEMBERS
=
(
uids
:
string
[])
=>
Promise
<
void
>
;
export
const
ACTION_CHAT_REMOVE_MEMBER
=
"移除成员"
;
export
type
ACTION_CHAT_REMOVE_MEMBER
=
(
uids
:
string
[])
=>
Promise
<
void
>
export
type
ACTION_CHAT_REMOVE_MEMBER
=
(
uids
:
string
[])
=>
Promise
<
void
>
;
export
const
ACTION_CHAT_ADD_CS
=
"添加客服"
;
export
type
ACTION_CHAT_ADD_CS
=
(
uids
:
string
[])
=>
Promise
<
void
>
export
type
ACTION_CHAT_ADD_CS
=
(
uids
:
string
[])
=>
Promise
<
void
>
;
export
const
ACTION_CHAT_START_RECEPTION
=
"开始接待"
;
export
type
ACTION_CHAT_START_RECEPTION
=
()
=>
Promise
<
void
>
export
type
ACTION_CHAT_START_RECEPTION
=
()
=>
Promise
<
void
>
;
export
const
ACTION_CHAT_FINISH_RECEPTION
=
"结束接待"
;
export
type
ACTION_CHAT_FINISH_RECEPTION
=
()
=>
Promise
<
void
>
export
type
ACTION_CHAT_FINISH_RECEPTION
=
()
=>
Promise
<
void
>
;
export
const
ACTION_CHAT_REMOVE_CS
=
"移除客服"
;
export
type
ACTION_CHAT_REMOVE_CS
=
(
uids
:
string
[])
=>
Promise
<
void
>
export
type
ACTION_CHAT_REMOVE_CS
=
(
uids
:
string
[])
=>
Promise
<
void
>
;
export
const
ACTION_CHAT_USER_EXIT
=
"普通用户退出"
;
export
type
ACTION_CHAT_USER_EXIT
=
()
=>
Promise
<
void
>
export
type
ACTION_CHAT_USER_EXIT
=
()
=>
Promise
<
void
>
;
export
const
ACTION_CHAT_CS_EXIT
=
"客服退出"
;
export
type
ACTION_CHAT_CS_EXIT
=
()
=>
Promise
<
void
>
export
type
ACTION_CHAT_CS_EXIT
=
()
=>
Promise
<
void
>
;
}
export
interface
ChatStoreState
{
...
...
@@ -338,13 +355,13 @@ export interface ChatStoreState {
[
ChatStore
.
STATE_CURRENT_CHAT_INITING
]:
ChatStore
.
STATE_CURRENT_CHAT_INITING
;
[
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
]:
ChatStore
.
STATE_MY_CHAT_ROOM_LIST
;
[
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
]:
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
;
[
ChatStore
.
STATE_CHAT_DIALOG_IS_SINGLE
]:
ChatStore
.
STATE_CHAT_DIALOG_IS_SINGLE
[
ChatStore
.
STATE_SINGLE_CHAT
]:
ChatStore
.
STATE_SINGLE_CHAT
[
ChatStore
.
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
]:
ChatStore
.
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
[
ChatStore
.
STATE_CHAT_CURRENT_USER_UID
]:
ChatStore
.
STATE_CHAT_CURRENT_USER_UID
[
ChatStore
.
STATE_CHAT_CURRENT_USER_TYPE
]:
ChatStore
.
STATE_CHAT_CURRENT_USER_TYPE
[
ChatStore
.
STATE_CHAT_SEND_FAIL_MESSAGE
]:
ChatStore
.
STATE_CHAT_SEND_FAIL_MESSAGE
[
ChatStore
.
STATE_CHAT_USERNAME
]:
ChatStore
.
STATE_CHAT_USERNAME
[
ChatStore
.
STATE_CHAT_DIALOG_IS_SINGLE
]:
ChatStore
.
STATE_CHAT_DIALOG_IS_SINGLE
;
[
ChatStore
.
STATE_SINGLE_CHAT
]:
ChatStore
.
STATE_SINGLE_CHAT
;
[
ChatStore
.
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
]:
ChatStore
.
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
;
[
ChatStore
.
STATE_CHAT_CURRENT_USER_UID
]:
ChatStore
.
STATE_CHAT_CURRENT_USER_UID
;
[
ChatStore
.
STATE_CHAT_CURRENT_USER_TYPE
]:
ChatStore
.
STATE_CHAT_CURRENT_USER_TYPE
;
[
ChatStore
.
STATE_CHAT_SEND_FAIL_MESSAGE
]:
ChatStore
.
STATE_CHAT_SEND_FAIL_MESSAGE
;
[
ChatStore
.
STATE_CHAT_USERNAME
]:
ChatStore
.
STATE_CHAT_USERNAME
;
}
export
const
chatStore
=
namespace
(
ChatStore
.
ns
);
xim/index.ts
View file @
452addc8
...
...
@@ -35,8 +35,8 @@ class Chat {
EmojiService
.
raiseOnReady
(
this
.
token
);
option
.
sdk
().
events
.
addTokenChanged
(
token
=>
{
this
.
setToken
(()
=>
new
Promise
(
resolve
=>
resolve
(
token
)));
option
.
sdk
().
events
.
addTokenChanged
(
(
token
)
=>
{
this
.
setToken
(()
=>
new
Promise
(
(
resolve
)
=>
resolve
(
token
)));
});
return
this
.
initChatSdk
(
option
.
webSocketUri
);
...
...
@@ -49,7 +49,7 @@ class Chat {
public
getSdk
=
()
=>
{
if
(
this
.
_sdk
==
null
)
{
throw
new
Error
(
"sdk shouldn't undefined"
);
}
;
}
return
this
.
_sdk
();
};
...
...
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