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
55070bdd
authored
Oct 08, 2021
by
Sixong.Zhu
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
update
parent
d9ff54d3
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
245 additions
and
19 deletions
components/message-item/text-message.vue
components/message.vue
database/index.ts
model/index.ts
store/index.ts
store/model.ts
xim/index.ts
xim/models/chat.ts
components/message-item/text-message.vue
View file @
55070bdd
...
...
@@ -7,6 +7,7 @@
<
script
lang=
"ts"
>
import
{
replaceText2Link
}
from
"@/customer-service/utils"
;
import
xim
from
"@/customer-service/xim"
;
import
{
Component
}
from
"vue-property-decorator"
;
import
BaseMessage
from
"./index"
;
...
...
@@ -15,9 +16,21 @@ export default class Index extends BaseMessage {
private
readonly
emptyText
=
" "
;
private
format2Link
(
text
:
string
)
{
return
replaceText2Link
(
text
);
let
t
=
replaceText2Link
(
text
);
const
keywords
=
xim
.
getMatchedTextKeywords
();
for
(
const
item
of
keywords
)
{
const
r
=
new
RegExp
(
item
,
"g"
);
t
=
t
.
replace
(
r
,
`<span class="highlight">
${
item
}
</span>`
);
}
return
t
;
}
}
</
script
>
<
style
lang=
"less"
scoped
></
style
>
<
style
lang=
"less"
scoped
>
.inline-text
{
/deep/
.highlight
{
color
:
#e87005
;
}
}
</
style
>
components/message.vue
View file @
55070bdd
...
...
@@ -4,6 +4,7 @@
:class=
"
{
'my-message flex-row-reverse': isMyMessage,
'justify-content-center': isWithdrawMessage,
'offset-bottom': matchKeywords,
}"
>
<div
class=
"msg-content"
:class=
"
{ 'algin-left': !isMyMessage }">
...
...
@@ -31,7 +32,7 @@
></i>
<i
class=
"el-icon-loading"
v-else-if=
"isSendingMessage"
></i>
<template
v-if=
"showReadSummary && !isWithdrawMessage"
>
<template
v-if=
"
backend &&
showReadSummary && !isWithdrawMessage"
>
<div
v-if=
"isMyMessage"
class=
"msg-read pos-rel"
>
<span
@
click=
"openReaderList"
...
...
@@ -59,6 +60,36 @@
@
click=
"withdraw"
>
撤回此消息
</span
>
<el-popover
:visible-arrow=
"false"
v-if=
"backend && matchKeywords"
placement=
"right"
popper-class=
"match-keyword-popover"
trigger=
"click"
:disabled=
"!!handled"
>
<ul
class=
"keyword-action"
>
<li
@
click
.
stop=
"executeHandled"
>
设为已处理
</li>
<li
@
click
.
stop=
"ignoredKeyword"
>
忽略
</li>
<!-- <li>创建工作流</li> -->
<!-- <li>更多</li> -->
</ul>
<span
slot=
"reference"
class=
"match-keyword d-flex align-items-center text-nowrap"
:class=
"{ handled: handled === 1, ignored: handled === 2 }"
>
<i
:class=
"handled ? 'el-icon-success' : 'el-icon-info'"
></i>
<span>
{{
handled
? handled === 1
? "已处理"
: "已忽略"
: "触发敏感词,请处理"
}}
</span>
</span>
</el-popover>
</div>
</template>
...
...
@@ -90,6 +121,7 @@ import AudioMessage from "./message-item/audio-message.vue";
import
VideoMessage
from
"./message-item/video-message.vue"
;
import
TextMessage
from
"./message-item/text-message.vue"
;
import
WithdrawMessage
from
"./message-item/withdraw-message.vue"
;
import
xim
from
"./../xim"
;
const
twoMinutes
=
2
*
60
*
1000
;
...
...
@@ -130,8 +162,11 @@ export default class Message extends Mixins(Filters) {
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_WITHDRAW
)
private
readonly
executeWithDraw
!
:
ChatStore
.
MUTATION_WITHDRAW
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_SET_HANDLED
)
private
readonly
setHandled
!
:
ChatStore
.
ACTION_SET_HANDLED
;
@
Prop
({
type
:
Object
,
default
:
()
=>
Object
.
create
(
null
)
})
private
data
!
:
dto
.
Message
;
private
readonly
data
!
:
dto
.
Message
;
@
Prop
()
private
readonly
isSendingMessage
!
:
boolean
;
...
...
@@ -144,6 +179,8 @@ export default class Message extends Mixins(Filters) {
@
Inject
({
default
:
false
})
readonly
showReadSummary
!
:
boolean
;
private
readonly
backend
=
chat
.
isBackend
();
private
messageComponent
=
""
;
private
readListVisibility
=
false
;
...
...
@@ -151,9 +188,10 @@ export default class Message extends Mixins(Filters) {
private
org
=
""
;
private
readerListOffset
=
false
;
private
defaultMessageHandledStatus
=
dto
.
MessageHandled
.
Default
;
private
get
canWithdraw
()
{
if
(
this
.
data
)
{
if
(
this
.
backend
&&
this
.
data
)
{
return
new
Date
().
valueOf
()
-
this
.
data
.
ts
*
1000
<
twoMinutes
;
}
return
false
;
...
...
@@ -182,6 +220,13 @@ export default class Message extends Mixins(Filters) {
return
{
msg
:
{
text
:
""
}
};
}
private
get
handled
()
{
if
(
this
.
data
)
{
return
this
.
defaultMessageHandledStatus
||
this
.
data
.
handled
;
}
return
dto
.
MessageHandled
.
Default
;
}
private
get
isMyMessage
()
{
if
(
this
.
isSendingMessage
)
{
return
true
;
...
...
@@ -196,10 +241,13 @@ export default class Message extends Mixins(Filters) {
}
private
get
userName
()
{
return
(
this
.
chatMembers
!
.
find
((
member
)
=>
member
.
eid
===
this
.
data
.
eid
)
?.
name
??
""
);
if
(
this
.
chatMembers
)
{
const
t
=
this
.
chatMembers
.
find
((
i
)
=>
i
.
eid
===
this
.
data
.
eid
);
if
(
t
)
{
return
t
.
name
;
}
}
return
""
;
}
private
get
avatar
()
{
...
...
@@ -250,6 +298,21 @@ export default class Message extends Mixins(Filters) {
return
type
;
}
private
get
isTextMessage
()
{
return
this
.
messageType
===
dto
.
MessageType
.
Text
;
}
private
get
matchKeywords
()
{
if
(
this
.
isTextMessage
&&
!
this
.
isMyMessage
)
{
const
m
=
this
.
messageBody
.
msg
as
{
text
:
string
};
if
(
m
&&
m
.
text
)
{
const
keywords
=
xim
.
getMatchedTextKeywords
();
return
keywords
.
find
((
i
)
=>
m
.
text
.
includes
(
i
));
}
}
return
false
;
}
private
isCustomer
()
{
return
!
this
.
showReadSummary
;
}
...
...
@@ -281,6 +344,28 @@ export default class Message extends Mixins(Filters) {
this
.
readerListOffset
=
e
.
x
<
450
;
this
.
readListVisibility
=
true
;
}
private
executeHandled
()
{
this
.
setHandled
({
id
:
this
.
data
.
id
,
value
:
(
this
.
defaultMessageHandledStatus
=
dto
.
MessageHandled
.
Handled
),
});
this
.
closeKeywordPopover
();
}
private
ignoredKeyword
()
{
this
.
setHandled
({
id
:
this
.
data
.
id
,
value
:
(
this
.
defaultMessageHandledStatus
=
dto
.
MessageHandled
.
Ignored
),
});
this
.
closeKeywordPopover
();
}
private
closeKeywordPopover
()
{
document
.
body
.
click
();
}
}
</
script
>
...
...
@@ -363,6 +448,10 @@ export default class Message extends Mixins(Filters) {
}
}
&
.offset-bottom
{
margin-bottom
:
30px
;
}
>
i
{
height
:
16px
;
font-size
:
16px
;
...
...
@@ -493,6 +582,7 @@ i.msg-avatar {
font-size
:
14px
;
}
}
.no-selection
{
user-select
:
none
;
}
...
...
@@ -512,4 +602,53 @@ i.msg-avatar {
.all
{
color
:
#4389f8
;
}
.match-keyword
{
background-color
:
#fff3e0
;
border-radius
:
13px
;
padding
:
3px
8px
;
color
:
#e87005
;
position
:
absolute
;
bottom
:
-10px
;
left
:
0
;
cursor
:
pointer
;
font-size
:
12px
;
&.handled
{
color
:
#59ba7b
;
background-color
:
#f0f0f0
;
cursor
:
default
;
}
&
.ignored
{
color
:
#999
;
background-color
:
#f0f0f0
;
cursor
:
default
;
}
i
{
margin-right
:
5px
;
}
}
.keyword-action
{
list-style
:
none
;
li
{
list-style
:
none
;
padding
:
8px
10px
;
color
:
#077aec
;
cursor
:
pointer
;
&:hover
{
background-color
:
#f5f6fa
;
}
}
}
</
style
>
<
style
lang=
"less"
>
.match-keyword-popover
{
padding
:
0
;
}
</
style
>
database/index.ts
View file @
55070bdd
import
{
Chat
,
Message
}
from
"./../xim/models/chat"
;
import
{
MessageHandled
}
from
"../model"
;
class
ChatCacheDatabaseController
{
private
db
!
:
IDBDatabase
;
...
...
@@ -212,6 +213,30 @@ class ChatCacheDatabaseController {
return
source
;
}
public
updateMessageStatus
(
chat
:
number
,
msg
:
number
,
status
:
MessageHandled
)
{
return
new
Promise
<
void
>
((
resolve
)
=>
{
if
(
!
this
.
db
)
{
return
resolve
();
}
this
.
setupChatMessageDatabase
(
chat
).
finally
(()
=>
{
const
store
=
this
.
buildChatMessageStore
(
chat
);
const
r
=
store
.
get
(
msg
);
r
.
onsuccess
=
(
o
)
=>
{
const
p
=
(
o
.
target
as
any
).
result
as
Message
;
p
.
handled
=
status
;
const
u
=
store
.
put
(
p
);
u
.
onsuccess
=
()
=>
resolve
();
u
.
onerror
=
()
=>
resolve
();
};
r
.
onerror
=
()
=>
resolve
();
});
});
}
}
export
const
dbController
=
new
ChatCacheDatabaseController
();
model/index.ts
View file @
55070bdd
...
...
@@ -88,6 +88,12 @@ export const enum MessageType {
Withdraw
=
"withdraw"
,
}
export
const
enum
MessageHandled
{
Default
,
Handled
,
Ignored
,
}
export
interface
Message
{
at_id
:
string
;
chat_id
:
number
;
...
...
@@ -108,6 +114,7 @@ export interface Message {
type
:
MessageType
;
update_time
:
number
;
url
:
string
;
handled
?:
MessageHandled
;
}
export
type
MessageRequestResult
=
readonly
Message
[];
...
...
store/index.ts
View file @
55070bdd
...
...
@@ -2,7 +2,7 @@ import Vue from "vue";
import
{
Module
}
from
"vuex"
;
import
{
dbController
}
from
"../database"
;
import
{
ChatMember
,
ServiceType
}
from
"../model"
;
import
{
ChatMember
,
ServiceType
,
MessageHandled
}
from
"../model"
;
import
{
isAccessibleUrl
}
from
"../service/tools"
;
import
{
unique
}
from
"../utils"
;
import
{
getChatModelInfo
}
from
"../utils/chat-info"
;
...
...
@@ -28,7 +28,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
);
});
}
...
...
@@ -301,7 +301,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
,
...
...
@@ -640,7 +640,8 @@ export default {
wantedChatRoom
.
business_data
.
model_name
,
wantedChatRoom
.
business_data
.
obj_id
,
wantedChatRoom
.
business_data
.
detail_name
).
then
(
info
=>
{
)
.
then
((
info
)
=>
{
commit
(
ChatStore
.
MUTATION_SAVE_CURRENT_CHAT_VERSION
,
info
.
uniplat_version
...
...
@@ -649,7 +650,8 @@ export default {
ChatStore
.
MUTATION_SAVE_CURRENT_CHAT_UNIPLAT_ID
,
info
.
uniplatId
);
}).
catch
(
console
.
error
)
})
.
catch
(
console
.
error
);
commit
(
ChatStore
.
MUTATION_INITING_CHAT
);
removeRegisterChatEvents
.
forEach
((
k
)
=>
k
());
...
...
@@ -710,7 +712,7 @@ 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
);
})
);
...
...
@@ -846,6 +848,24 @@ export default {
await
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
500
));
await
dispatch
(
ChatStore
.
ACTION_GET_CHAT_MEMBERS
);
},
[
ChatStore
.
ACTION_SET_HANDLED
](
{
state
},
p
:
{
id
:
number
;
value
:
MessageHandled
;
}
)
{
const
msgs
=
state
[
ChatStore
.
STATE_CHAT_MSG_HISTORY
];
if
(
msgs
)
{
const
t
=
msgs
.
find
((
i
)
=>
i
.
id
===
p
.
id
);
if
(
t
)
{
t
.
handled
=
p
.
value
;
const
chatId
=
state
[
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
];
chatId
&&
dbController
.
updateMessageStatus
(
chatId
,
p
.
id
,
p
.
value
);
}
}
},
},
getters
:
{
[
ChatStore
.
STATE_CHAT_MSG_HISTORY
](
state
)
{
...
...
store/model.ts
View file @
55070bdd
...
...
@@ -329,6 +329,12 @@ export namespace ChatStore {
export
const
ACTION_CHAT_CS_EXIT
=
"客服退出"
;
export
type
ACTION_CHAT_CS_EXIT
=
()
=>
Promise
<
void
>
;
export
const
ACTION_SET_HANDLED
=
"设置敏感词已处理"
;
export
type
ACTION_SET_HANDLED
=
(
p
:
{
id
:
number
;
value
:
dto
.
MessageHandled
;
})
=>
void
;
}
export
interface
ChatStoreState
{
...
...
xim/index.ts
View file @
55070bdd
...
...
@@ -20,6 +20,7 @@ class Chat {
private
serviceType
=
ServiceType
.
Backend
;
private
product
=
CustomerServiceProduct
.
Default
;
private
eventHub
:
Vue
|
null
=
null
;
private
keywords
:
string
[]
=
[];
private
userMapping
:
{
[
key
:
string
]:
{
name
:
string
;
avatar
:
string
}
}
=
{};
...
...
@@ -47,9 +48,13 @@ 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
)))
);
// this.keywords = ["社保"];
return
this
.
initChatSdk
(
option
.
webSocketUri
);
}
...
...
@@ -59,7 +64,7 @@ class Chat {
}
public
getSdk
=
()
=>
{
if
(
this
.
_sdk
==
null
)
{
if
(
!
this
.
_sdk
)
{
throw
new
Error
(
"sdk shouldn't undefined"
);
}
return
this
.
_sdk
();
...
...
@@ -69,6 +74,10 @@ class Chat {
return
this
.
serviceType
;
}
public
isBackend
()
{
return
this
.
serviceType
===
ServiceType
.
Backend
;
}
public
getProduct
()
{
return
this
.
product
;
}
...
...
@@ -133,6 +142,10 @@ class Chat {
this
.
eventHub
.
$on
(
event
,
callback
);
}
}
public
getMatchedTextKeywords
()
{
return
this
.
keywords
;
}
}
export
default
new
Chat
();
xim/models/chat.ts
View file @
55070bdd
import
{
MessageHandled
}
from
"@/customer-service/model"
;
export
interface
Chat
{
id
:
number
;
org_id
:
string
;
...
...
@@ -68,6 +70,7 @@ export interface Message {
status
:
number
;
url
:
string
;
is_open
:
boolean
;
handled
?:
MessageHandled
;
}
export
interface
NotifyMessage
{
...
...
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