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
561c7886
authored
Aug 07, 2021
by
吴云建
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
会话页面添加
parent
67148e4c
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
372 additions
and
52 deletions
chat.vue
components/chat-container.vue
components/chat-list-model.vue
components/chat-list.vue
components/message.vue
store/index.ts
store/model.ts
chat.vue
View file @
561c7886
<
template
>
<div
class=
"chat-con"
:class=
"
{ userMode }">
<ChatTitle
:close=
"hide"
class=
"chat-title"
/>
<div
class=
"chat-area-con"
:class=
"
{isSingle: isSingleChat}">
<div
class=
"chat-area-con"
:class=
"
{isSingle: isSingleChat
, needSearch: !modelName
}">
<div
v-if=
"chatId != null"
class=
"h-100 chat-area"
>
<chat-room
/>
</div>
...
...
@@ -44,7 +44,7 @@
</div>
</
template
>
<
script
lang=
"ts"
>
import
{
Component
,
Vue
,
Watch
}
from
"vue-property-decorator"
;
import
{
Component
,
Vue
,
Watch
,
Prop
}
from
"vue-property-decorator"
;
import
remarkList
from
"../components/common/remarkList.vue"
;
import
ChatMembers
from
"./components/chat-members.vue"
;
import
ChatRoom
from
"./components/chat-room.vue"
;
...
...
@@ -110,14 +110,19 @@ export default class Chat extends Vue {
this
.
refreshFlag
=
false
;
})
}
@
Prop
(
String
)
modelName
:
string
;
}
</
script
>
<
style
lang=
"less"
scoped
>
.chat-area-con
{
height
:
calc
(
100%
-
60px
)
;
height
:
100%
;
&.isSingle
{
height
:
70vh
;
}
&
.needSearch
{
height
:
calc
(
100%
-
60px
);
}
}
.chat-con
{
height
:
100%
;
...
...
@@ -148,7 +153,7 @@ export default class Chat extends Vue {
position
:
relative
;
width
:
var
(
--chat-panel-width
);
border-left
:
1px
solid
#e1e1e1
;
height
:
calc
(
100%
-
1
px
);
height
:
calc
(
100%
-
2
px
);
}
.buttons
{
position
:
absolute
;
...
...
components/chat-container.vue
View file @
561c7886
<
template
>
<div
class=
"chat-container"
:class=
"
{'is-in-page': isInPage}">
<div
class=
"search-wrap"
>
<div
class=
"search-wrap"
v-if=
"!modelName"
>
<el-input
class=
"keyword-input"
placeholder=
"昵称、手机、Email、备注"
...
...
@@ -12,16 +12,18 @@
></el-input>
<i
v-if=
"!isInPage"
class=
"close-btn el-icon-close"
@
click=
"$emit('close')"
></i>
</div>
<chat-list
:searchKeyword=
"searchKeyword"
ref=
"chatListComp"
/>
<chat-list
v-if=
"!modelName"
:searchKeyword=
"searchKeyword"
ref=
"chatListComp"
/>
<chat-list-model
v-if=
"modelName"
@
update-page-info=
"$emit('update-page-info', $event)"
/>
<div
class=
"chat-content-wrap"
>
<chat
v-if=
"chatVisible
"
/>
<chat
v-if=
"chatVisible
&& onShow"
:modelName=
"modelName"
/>
</div>
</div>
</
template
>
<
script
lang=
"ts"
>
import
{
Component
,
Prop
,
Ref
,
Vue
}
from
"vue-property-decorator"
;
import
{
Component
,
Prop
,
Ref
,
Vue
,
Watch
}
from
"vue-property-decorator"
;
import
ChatList
from
"@/customer-service/components/chat-list.vue"
;
import
ChatListModel
from
"@/customer-service/components/chat-list-model.vue"
;
import
chat
from
"@/customer-service/chat.vue"
;
import
{
ChatStore
,
chatStore
}
from
"@/customer-service/store/model"
;
import
buttonThrottle
from
"../utils/button-throttle"
;
...
...
@@ -30,11 +32,16 @@ import buttonThrottle from "../utils/button-throttle";
name
:
"ChatContainer"
,
components
:
{
chat
,
ChatList
ChatList
,
ChatListModel
}
})
export
default
class
ChatContainer
extends
Vue
{
@
Prop
(
Boolean
)
isInPage
:
boolean
;
@
Prop
(
String
)
modelName
:
string
;
private
onShow
=
false
;
private
orginPath
=
""
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
)
private
readonly
chatVisible
!
:
ChatStore
.
STATE_CHAT_DIALOG_VISIBLE
;
...
...
@@ -50,9 +57,22 @@ export default class ChatContainer extends Vue {
this
.
chatListComp
.
search
()
}
@
Watch
(
"$route"
)
routeUpdate
()
{
if
(
this
.
$route
.
fullPath
!==
this
.
orginPath
)
{
this
.
onShow
=
false
;
}
else
{
this
.
onShow
=
true
}
}
private
onChatDrawerClose
(){
this
.
hideChat
()
}
created
()
{
this
.
orginPath
=
this
.
$route
.
fullPath
;
this
.
onShow
=
true
}
}
</
script
>
...
...
@@ -60,7 +80,7 @@ export default class ChatContainer extends Vue {
.chat-container
{
height
:
70vh
;
&.is-in-page
{
height
:
100%
;
height
:
calc
(
100%
-
45px
)
;
}
}
.keyword-input
{
...
...
components/chat-list-model.vue
0 → 100644
View file @
561c7886
<
template
>
<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"
>
<div
v-for=
"item in chatRooms"
:key=
"'room_' + item.id"
class=
"chat-item"
:class=
"
{ selected: isSelected(item) }"
@click="goToChatRoom(item)"
>
<div
class=
"chat-info"
>
<div
class=
"
chat-info-left
d-flex
justify-content-between
align-items-center
"
>
<div
class=
"chat-name flex-fill text-dot-dot-dot"
>
<span>
{{
item
.
title
}}
</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"
>
{{
parseMesage
(
item
)
}}
</div>
</div>
</div>
<div
class=
"empty"
v-if=
"chatRooms && chatRooms.length
<
=
0
"
>
无接待
</div>
</el-scrollbar>
<el-pagination
background
@
size-change=
"handleSizeChange"
:page-size=
"pageSize"
:total=
"total"
:current-page
.
sync=
"currentPage"
@
current-change=
"getList"
:page-sizes=
"[10, 20, 50]"
layout=
"total, prev, pager, next"
></el-pagination>
</div>
</div>
</div>
</
template
>
<
script
lang=
"ts"
>
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"
;
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
`[不支持的消息格式]`
;
}
}
@
Component
({
components
:
{}
})
export
default
class
ModelChatList
extends
Vue
{
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
)
private
readonly
_createChat
!
:
ChatStore
.
ACTION_CREATE_NEW_CHAT_BY_SERVICE_MAN
;
private
chatList
:
ChatType
[]
=
[];
@
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_SET_CHAT_SOURCE
)
private
readonly
setSource
!
:
ChatStore
.
MUTATION_SET_CHAT_SOURCE
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SHOW_CHAT
)
private
readonly
showChat
:
ChatStore
.
MUTATION_SHOW_CHAT
;
@
Prop
({
type
:
Number
,
default
:
-
1
})
private
selected
!
:
number
;
@
Ref
(
"scrollbar"
)
private
scrollbar
:
Vue
&
{
update
:
()
=>
void
};
private
modelName
=
""
;
private
listName
=
""
;
private
activeId
=
""
;
private
pageSize
=
10
;
private
total
=
0
;
private
currentPage
=
1
;
private
get
chatRooms
()
{
return
this
.
chatList
||
[];
}
private
isSelected
(
item
:
ChatType
)
{
if
(
this
.
chatId
)
{
return
item
.
chat_id
===
this
.
chatId
;
}
return
this
.
selected
===
item
.
chat_id
;
}
private
async
getList
()
{
const
result
=
await
this
.
sdk
.
model
(
this
.
modelName
).
list
(
this
.
listName
||
undefined
).
query
({
pageIndex
:
1
,
item_size
:
50
})
this
.
chatList
=
result
.
pageData
.
rows
.
map
(
it
=>
{
return
{
id
:
it
.
id
.
value
,
business_data
:
{
model_name
:
it
.
ModelName
.
value
,
obj_id
:
it
.
ObjId
.
value
,
detail_name
:
""
,
},
last_msg_content
:
it
.
LastMsgContent
.
value
,
last_msg_ts
:
it
.
LastMsgTime
.
value
,
last_msg_type
:
it
.
LastMsgType
.
value
,
title
:
it
.
title
.
value
}
as
ChatType
})
this
.
total
=
result
.
pageData
.
record_count
;
this
.
$emit
(
"update-page-info"
,
{
title
:
result
.
pageData
.
title
})
}
async
created
()
{
this
.
modelName
=
this
.
$route
.
params
.
modelName
;
this
.
listName
=
this
.
$route
.
params
.
listName
;
await
this
.
getList
();
this
.
setSource
(
ChatStore
.
StateChatSourceDirection
.
Server
);
this
.
scrollbar
.
update
();
}
mounted
()
{
this
.
saveMyId
();
}
private
handleSizeChange
(
newPageSize
)
{
this
.
pageSize
=
newPageSize
this
.
getList
()
}
private
async
goToChatRoom
(
data
:
ChatType
)
{
await
this
.
_createChat
({
modelName
:
data
.
business_data
.
model_name
,
selectedListId
:
data
.
business_data
.
obj_id
,
uids
:[],
showByPage
:
true
});
this
.
activeId
=
data
.
id
.
toString
();
}
private
raiseChatIdChanged
()
{
this
.
$emit
(
"change"
);
}
private
parseMesage
(
data
:
ChatType
)
{
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
goToDetail
(
model_name
:
string
,
keyvalue
:
string
)
{
this
.
$router
.
push
(
`/
${
this
.
$route
.
params
.
project
}
/
${
this
.
$route
.
params
.
entrance
}
/detail/
${
model_name
}
/key/
${
keyvalue
}
`
);
}
}
</
script
>
<
style
lang=
"less"
scoped
>
.chat-list-con
{
display
:
inline-block
;
width
:
25%
;
box-sizing
:
border-box
;
height
:
100%
;
border-right
:
1px
solid
#ccc
;
.title
{
padding-left
:
20px
;
line-height
:
59px
;
font-size
:
18px
;
border-bottom
:
1px
solid
#e1e1e1
;
}
}
.chat-list
{
text-align
:
center
;
}
.chat-list-scroll
{
height
:
100%
;
.empty
{
padding-top
:
100%
;
}
.list-scroll
{
height
:
calc
(
100%
-
88px
);
}
}
.keyword-input
{
width
:
200px
;
margin
:
15px
0
;
/deep/
.el-input__inner
{
font-size
:
13px
;
height
:
30px
;
line-height
:
30px
;
border-radius
:
15px
;
padding-right
:
15px
;
}
/
deep
/
.el-icon-time
{
background
:
transparent
;
}
}
.chat-list
{
.chat-item
{
cursor
:
pointer
;
padding
:
4px
15px
10px
;
border-bottom
:
1px
solid
#eee
;
&:hover
{
background
:
#e4f0ff
;
}
&
.selected
{
background
:
#f0f0f0
;
}
.chat-avatar
{
display
:
inline-block
;
vertical-align
:
middle
;
width
:
0
;
height
:
0
;
margin-right
:
5px
;
}
.chat-info
{
display
:
inline-block
;
vertical-align
:
middle
;
width
:
calc
(
100%
-
10px
);
.chat-name
{
line-height
:
35px
;
font-size
:
15px
;
}
}
.chat-info-left
{
text-align
:
start
;
font-size
:
14px
;
line-height
:
20px
;
color
:
#333333
;
margin-bottom
:
10px
;
.chat-time
{
text-align
:
end
;
flex
:
none
;
color
:
#999999
;
margin-left
:
10px
;
font-size
:
12px
;
line-height
:
1
;
}
}
.chat-msg
{
color
:
#888
;
text-align
:
start
;
font-size
:
12px
;
margin-top
:
-13px
;
}
}
}
.chat-check-detail
{
margin-left
:
10px
;
}
</
style
>
components/chat-list.vue
View file @
561c7886
...
...
@@ -28,7 +28,7 @@
:title=
"item.customer_name"
class=
"chat-name flex-fill text-dot-dot-dot"
>
<span>
{{
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
)
}}
...
...
@@ -195,7 +195,7 @@ export default class ChatList extends Vue {
display
:
inline-block
;
width
:
25%
;
box-sizing
:
border-box
;
height
:
calc
(
100%
-
60
px
);
height
:
calc
(
100%
-
59
px
);
border-right
:
1px
solid
#ccc
;
.title
{
padding-left
:
20px
;
...
...
components/message.vue
View file @
561c7886
...
...
@@ -3,8 +3,8 @@
class=
"message-con d-flex align-items-center"
:class=
"isMyMessage ? 'my-message flex-row-reverse' : ''"
>
<div
class=
"msg-content"
>
<div
class=
"msg-name no-selection"
>
{{
send
erName
}}
</div>
<div
class=
"msg-content"
:class=
"
{'algin-left': !isMyMessage}"
>
<div
class=
"msg-name no-selection"
:class=
"
{'algin-left': !isMyMessage}">
{{
us
erName
}}
</div>
<!-- Image -->
<div
class=
"msg-detail image-message"
...
...
@@ -158,8 +158,8 @@ import { chatStore, ChatStore } from "@/customer-service/store/model";
components
:
{
FileIcon
,
VoiceIcon
,
WhoReadList
,
VideoPlayerIcon
,
avatar
},
})
export
default
class
Message
extends
Mixins
(
Filters
)
{
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_
MY_
ID
)
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_
MY_
ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_
CURRENT_USER_U
ID
)
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_
CURRENT_USER_U
ID
;
@
chatStore
.
Getter
(
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
)
private
readonly
chatMembers
!
:
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
;
...
...
@@ -177,7 +177,6 @@ export default class Message extends Mixins(Filters) {
private
fileFailed2Load
=
false
;
private
image404
=
FileType
.
Image_404
;
private
loadingRealUrl
=
false
;
private
senderName
=
""
;
@
Prop
({
type
:
Object
,
default
:
()
=>
Object
.
create
(
null
)
})
private
data
!
:
dto
.
Message
;
...
...
@@ -236,44 +235,36 @@ export default class Message extends Mixins(Filters) {
}
const
senderEid
=
this
.
messageBody
.
eid
;
if
(
this
.
messageBody
)
{
const
msg
=
this
.
messageBody
;
if
(
this
.
chatSource
)
{
const
source
=
msg
.
msg
.
source
;
if
(
source
)
{
return
(
source
===
this
.
chatSource
&&
senderEid
===
this
.
chatMyId
);
}
if
(
this
.
org
&&
this
.
messageBody
.
oid
)
{
return
(
this
.
messageBody
.
oid
===
this
.
org
&&
senderEid
===
this
.
chatMyId
);
}
return
false
;
}
return
senderEid
===
this
.
chatMyId
;
}
return
false
;
}
private
created
()
{
this
.
getUserName
(
this
.
data
.
eid
);
return
senderEid
.
toString
()
===
this
.
chatMyId
.
toString
();
// if (this.messageBody) {
// const msg = this.messageBody;
// if (this.chatSource) {
// const source = msg.msg.source;
// if (source) {
// return (
// source === this.chatSource &&
// senderEid === this.chatMyId
// );
// }
// if (this.org && this.messageBody.oid) {
// return (
// this.messageBody.oid === this.org &&
// senderEid === this.chatMyId
// );
// }
// return false;
// }
// return senderEid === this.chatMyId;
// }
// return false;
}
private
mounted
()
{
this
.
buildMessageUrl
()
}
private
get
UserName
(
eid
:
string
)
{
this
.
senderName
=
this
.
chatMembers
.
find
(
member
=>
member
.
eid
===
eid
)?.
name
??
""
;
private
get
userName
(
)
{
return
this
.
chatMembers
.
find
(
member
=>
member
.
eid
===
this
.
data
.
eid
)?.
name
??
""
;
}
private
get
avatar
()
{
...
...
@@ -511,6 +502,16 @@ i.msg-avatar {
text-align
:
right
;
margin-bottom
:
3px
;
min-height
:
16px
;
&.algin-left
{
text-align
:
left
;
}
}
.msg-content
{
text-align
:
right
;
&.algin-left
{
text-align
:
left
;
}
}
.msg-detail
{
...
...
store/index.ts
View file @
561c7886
...
...
@@ -425,10 +425,10 @@ export default {
const
{
imChatId
}
=
await
sdk
()
.
model
(
params
.
modelName
)
.
chat
(
+
params
.
selectedListId
,
orgId
())
.
createChat
(
"这是名字,要改"
);
.
createChat
();
const
chatId
=
Number
(
imChatId
);
await
dispatch
(
ChatStore
.
ACTION_GET_MY_CHAT_LIST
);
commit
(
ChatStore
.
MUTATION_SHOW_CHAT
,
tru
e
);
commit
(
ChatStore
.
MUTATION_SHOW_CHAT
,
!
params
.
showByPag
e
);
await
dispatch
(
ChatStore
.
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
,
chatId
);
},
async
[
ChatStore
.
ACTION_CREATE_NEW_CHAT_BY_CLIENT
](
...
...
@@ -438,7 +438,7 @@ export default {
const
{
imChatId
}
=
await
sdk
()
.
model
(
params
.
modelName
)
.
chat
(
+
params
.
selectedListId
,
orgId
())
.
createChat
(
"这是名字,要改"
,
true
);
.
createChat
(
true
);
const
chatId
=
Number
(
imChatId
);
await
commit
(
ChatStore
.
MUTATION_SHOW_CHAT
,
true
);
await
dispatch
(
ChatStore
.
ACTION_SAVE_CURRENT_CHAT_ID_VERSION
,
chatId
);
...
...
store/model.ts
View file @
561c7886
...
...
@@ -242,6 +242,7 @@ export namespace ChatStore {
modelName
:
string
;
selectedListId
:
string
;
uids
:
string
[];
showByPage
?:
boolean
;
})
=>
Promise
<
void
>
export
const
ACTION_CREATE_NEW_CHAT_BY_CLIENT
=
"顾客向客服发起新会话"
;
...
...
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