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
1f1dc90c
authored
Jul 13, 2021
by
panjiangyi
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge branch 'master' of
http://gitlab.corp.qinqinxiaobao.com:9880/uniplat/customer-service
parents
15b87e11
1afba5a5
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
547 additions
and
548 deletions
chat-list.vue
chat-room.vue
chat.vue
components/file-icon.vue
components/image-preview.vue
components/message.vue
components/video-preview.vue
components/who-read-list.vue
create-chat.vue
hybrid-input/index.vue
message-list.vue
chat-list.vue
View file @
1f1dc90c
...
...
@@ -207,7 +207,7 @@ export default class ChatList extends Vue {
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.chat-list-con
{
.title
{
padding-left
:
20px
;
...
...
chat-room.vue
View file @
1f1dc90c
...
...
@@ -178,7 +178,7 @@ export default class ChatRoom extends Vue {
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.room-title
{
font-size
:
16px
;
padding
:
0
20px
;
...
...
@@ -270,7 +270,7 @@ export default class ChatRoom extends Vue {
}
}
.chat-area
{
$
input-height
:
130px
;
--
input-height
:
130px
;
.chat-messages
{
height
:
calc
(
100%
-
130px
+
1px
);
...
...
@@ -278,7 +278,7 @@ export default class ChatRoom extends Vue {
}
.chat-input
{
height
:
$
input-height
;
height
:
var
(
--input-height
)
;
}
}
.order-info-con
{
...
...
chat.vue
View file @
1f1dc90c
...
...
@@ -46,7 +46,7 @@ export default class Chat extends Vue {
}
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.chat-dialog-con
{
/deep/
.el-dialog__header
{
display
:
none
;
...
...
components/file-icon.vue
View file @
1f1dc90c
<
template
>
<span
class=
"file-icon"
:title=
"value"
v-html=
"html"
></span>
<span
class=
"file-icon"
:title=
"value"
v-html=
"html"
></span>
</
template
>
<
script
lang=
"ts"
>
import
{
Component
,
Model
,
Vue
}
from
"vue-property-decorator"
;
import
{
Component
,
Model
,
Vue
}
from
"vue-property-decorator"
;
import
{
FileType
,
getSvg
}
from
"./file-controller"
;
@
Component
({
components
:
{}
})
export
default
class
FileIcon
extends
Vue
{
@
Model
(
"update"
)
private
value
!
:
FileType
@
Model
(
"update"
)
private
value
!
:
FileType
;
private
get
audio
()
{
return
this
.
value
===
FileType
.
Audio
;
}
private
get
audio
()
{
return
this
.
value
===
FileType
.
Audio
;
}
private
get
excel
()
{
return
this
.
value
===
FileType
.
Excel
;
}
private
get
excel
()
{
return
this
.
value
===
FileType
.
Excel
;
}
private
get
image
()
{
return
this
.
value
===
FileType
.
Image
;
}
private
get
image
()
{
return
this
.
value
===
FileType
.
Image
;
}
private
get
others
()
{
return
this
.
value
===
FileType
.
Others
;
}
private
get
others
()
{
return
this
.
value
===
FileType
.
Others
;
}
private
get
pdf
()
{
return
this
.
value
===
FileType
.
Pdf
;
}
private
get
pdf
()
{
return
this
.
value
===
FileType
.
Pdf
;
}
private
get
ppt
()
{
return
this
.
value
===
FileType
.
Ppt
;
}
private
get
ppt
()
{
return
this
.
value
===
FileType
.
Ppt
;
}
private
get
rp
()
{
return
this
.
value
===
FileType
.
Rp
;
}
private
get
rp
()
{
return
this
.
value
===
FileType
.
Rp
;
}
private
get
txt
()
{
return
this
.
value
===
FileType
.
Txt
;
}
private
get
txt
()
{
return
this
.
value
===
FileType
.
Txt
;
}
private
get
video
()
{
return
this
.
value
===
FileType
.
Video
;
}
private
get
video
()
{
return
this
.
value
===
FileType
.
Video
;
}
private
get
word
()
{
return
this
.
value
===
FileType
.
Word
;
}
private
get
word
()
{
return
this
.
value
===
FileType
.
Word
;
}
private
get
xmid
()
{
return
this
.
value
===
FileType
.
Xmind
;
}
private
get
xmid
()
{
return
this
.
value
===
FileType
.
Xmind
;
}
private
get
zip
()
{
return
this
.
value
===
FileType
.
Zip
;
}
private
get
zip
()
{
return
this
.
value
===
FileType
.
Zip
;
}
private
get
html
()
{
return
getSvg
(
this
.
value
);
}
private
get
html
()
{
return
getSvg
(
this
.
value
);
}
}
</
script
>
<
style
lang=
"
scss"
>
<
style
lang=
"
less"
scoped
>
.file-icon
{
margin-left
:
10px
;
margin-left
:
10px
;
svg
{
max-width
:
36px
;
max-height
:
36px
;
}
svg
{
max-width
:
36px
;
max-height
:
36px
;
}
}
</
style
>
components/image-preview.vue
View file @
1f1dc90c
<
template
>
<el-dialog
:modal=
"false"
:before-close=
"close"
:visible=
"value"
custom-class=
"hide-header show-close padding-0 width-auto"
>
<div
class=
"d-flex flex-column"
>
<div
class=
"preview-title text-center"
>
图片预览
</div>
<el-dialog
:modal=
"false"
:before-close=
"close"
:visible=
"value"
custom-class=
"hide-header show-close padding-0 width-auto"
>
<div
class=
"d-flex flex-column"
>
<div
class=
"preview-title text-center"
>
图片预览
</div>
<div
class=
"d-flex justify-content-center"
style=
"min-width: 300px"
>
<img
v-if=
"file"
:src=
"file.url"
:style=
"style"
/>
</div>
<div
class=
"d-flex justify-content-center"
style=
"min-width: 300px"
>
<img
v-if=
"file"
:src=
"file.url"
:style=
"style"
/>
</div>
<div
class=
"d-flex justify-content-center actions"
>
<span
class=
"d-flex align-items-center justify-content-center"
@
click=
"set2Default"
>
1:1
</span
>
<a
class=
"d-flex align-items-center justify-content-center"
:href=
"file.url | downloadUrl(getAttachment)"
:download=
"getAttachment"
>
<i
class=
"el-icon-download"
></i>
</a>
</div>
</div>
</el-dialog>
<div
class=
"d-flex justify-content-center actions"
>
<span
class=
"d-flex align-items-center justify-content-center"
@
click=
"set2Default"
>
1:1
</span
>
<a
class=
"d-flex align-items-center justify-content-center"
:href=
"file.url | downloadUrl(getAttachment)"
:download=
"getAttachment"
>
<i
class=
"el-icon-download"
></i>
</a>
</div>
</div>
</el-dialog>
</
template
>
<
script
lang=
"ts"
>
...
...
@@ -35,73 +35,72 @@ import { Component, Model, Prop, Vue } from "vue-property-decorator";
@
Component
({
components
:
{}
})
export
default
class
ImagePreview
extends
Vue
{
@
Model
(
"update"
)
private
value
!
:
boolean
@
Model
(
"update"
)
private
value
!
:
boolean
;
@
Prop
()
private
file
!
:
{
name
:
string
;
url
:
string
}
@
Prop
()
private
file
!
:
{
name
:
string
;
url
:
string
};
private
style
:
{
"max-height"
:
number
|
string
;
"max-width"
:
number
|
string
;
}
=
{
"max-height"
:
"300px"
,
"max-width"
:
"600px"
,
}
private
style
:
{
"max-height"
:
number
|
string
;
"max-width"
:
number
|
string
;
}
=
{
"max-height"
:
"300px"
,
"max-width"
:
"600px"
,
};
private
close
()
{
setTimeout
(
()
=>
(
this
.
style
=
{
"max-height"
:
"300px"
,
"max-width"
:
"600px"
}),
300
);
this
.
$emit
(
"update"
,
false
);
}
private
close
()
{
setTimeout
(
()
=>
(
this
.
style
=
{
"max-height"
:
"300px"
,
"max-width"
:
"600px"
}),
300
);
this
.
$emit
(
"update"
,
false
);
}
private
set2Default
()
{
this
.
style
=
{
"max-height"
:
"1600px"
,
"max-width"
:
"1600px"
};
}
private
set2Default
()
{
this
.
style
=
{
"max-height"
:
"1600px"
,
"max-width"
:
"1600px"
};
}
private
get
getAttachment
()
{
if
(
this
.
file
)
{
return
this
.
file
.
name
;
}
return
"文件下载"
;
private
get
getAttachment
()
{
if
(
this
.
file
)
{
return
this
.
file
.
name
;
}
return
"文件下载"
;
}
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.preview-title
{
font-size
:
18px
;
color
:
#333
;
margin-bottom
:
15px
;
font-size
:
18px
;
color
:
#333
;
margin-bottom
:
15px
;
}
.actions
{
margin
:
15px
0
;
margin
:
15px
0
;
>
span,
a
{
width
:
30px
;
height
:
30px
;
background-color
:
#7a7b7d
;
color
:
#fff
;
border-radius
:
50%
;
cursor
:
pointer
;
>
span,
a
{
width
:
30px
;
height
:
30px
;
background-color
:
#7a7b7d
;
color
:
#fff
;
border-radius
:
50%
;
cursor
:
pointer
;
i
{
color
:
#fff
;
font-size
:
20px
;
}
&
+
span
{
margin-left
:
15px
;
}
i
{
color
:
#fff
;
font-size
:
20px
;
}
>
a
{
margin-left
:
15px
;
&
+
span
{
margin-left
:
15px
;
}
}
>
a
{
margin-left
:
15px
;
}
}
</
style
>
components/message.vue
View file @
1f1dc90c
...
...
@@ -430,7 +430,7 @@ export default class Message extends Vue {
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.message-con
{
margin
:
20px
0
;
...
...
components/video-preview.vue
View file @
1f1dc90c
<
template
>
<el-dialog
:modal=
"false"
:before-close=
"close"
:visible=
"value"
custom-class=
"hide-header show-close padding-0 width-auto"
>
<div
class=
"d-flex flex-column"
>
<div
class=
"preview-title text-center"
>
视频预览
</div>
<div
class=
"d-flex justify-content-center"
style=
"min-width: 300px"
>
<video
ref=
"video"
v-if=
"file"
:src=
"file.url"
controls
:style=
"style"
></video>
</div>
<div
class=
"d-flex justify-content-center actions"
>
<span
class=
"d-flex align-items-center justify-content-center"
@
click=
"set2Default"
>
1:1
</span
>
<a
class=
"d-flex align-items-center justify-content-center"
:href=
"file.url | downloadUrl(getAttachment)"
:download=
"getAttachment"
>
<i
class=
"el-icon-download"
></i>
</a>
</div>
</div>
</el-dialog>
<el-dialog
:modal=
"false"
:before-close=
"close"
:visible=
"value"
custom-class=
"hide-header show-close padding-0 width-auto"
>
<div
class=
"d-flex flex-column"
>
<div
class=
"preview-title text-center"
>
视频预览
</div>
<div
class=
"d-flex justify-content-center"
style=
"min-width: 300px"
>
<video
ref=
"video"
v-if=
"file"
:src=
"file.url"
controls
:style=
"style"
></video>
</div>
<div
class=
"d-flex justify-content-center actions"
>
<span
class=
"d-flex align-items-center justify-content-center"
@
click=
"set2Default"
>
1:1
</span
>
<a
class=
"d-flex align-items-center justify-content-center"
:href=
"file.url | downloadUrl(getAttachment)"
:download=
"getAttachment"
>
<i
class=
"el-icon-download"
></i>
</a>
</div>
</div>
</el-dialog>
</
template
>
<
script
lang=
"ts"
>
import
{
Component
,
Model
,
Prop
,
Ref
,
Vue
,
Watch
}
from
"vue-property-decorator"
;
import
{
Component
,
Model
,
Prop
,
Ref
,
Vue
,
Watch
,
}
from
"vue-property-decorator"
;
@
Component
({
components
:
{}
})
export
default
class
VideoPreview
extends
Vue
{
@
Model
(
"update"
)
private
value
!
:
boolean
@
Prop
()
private
file
!
:
{
name
:
string
;
url
:
string
}
@
Ref
(
"video"
)
private
video
!
:
HTMLVideoElement
private
style
:
{
"max-height"
:
number
|
string
;
"max-width"
:
number
|
string
;
}
=
{
"max-height"
:
"800px"
,
"max-width"
:
"800px"
,
@
Model
(
"update"
)
private
value
!
:
boolean
;
@
Prop
()
private
file
!
:
{
name
:
string
;
url
:
string
};
@
Ref
(
"video"
)
private
video
!
:
HTMLVideoElement
;
private
style
:
{
"max-height"
:
number
|
string
;
"max-width"
:
number
|
string
;
}
=
{
"max-height"
:
"800px"
,
"max-width"
:
"800px"
,
};
private
close
()
{
setTimeout
(
()
=>
(
this
.
style
=
{
"max-height"
:
"300px"
,
"max-width"
:
"600px"
}),
300
);
this
.
$emit
(
"update"
,
false
);
}
private
set2Default
()
{
this
.
style
=
{
"max-height"
:
"1600px"
,
"max-width"
:
"1600px"
};
}
private
get
getAttachment
()
{
if
(
this
.
file
)
{
return
this
.
file
.
name
;
}
private
close
()
{
setTimeout
(
()
=>
(
this
.
style
=
{
"max-height"
:
"300px"
,
"max-width"
:
"600px"
}),
300
);
this
.
$emit
(
"update"
,
false
);
}
private
set2Default
()
{
this
.
style
=
{
"max-height"
:
"1600px"
,
"max-width"
:
"1600px"
};
}
private
get
getAttachment
()
{
if
(
this
.
file
)
{
return
this
.
file
.
name
;
}
return
"视频下载"
;
}
@
Watch
(
"value"
)
private
onOpen
()
{
if
(
this
.
value
)
{
this
.
video
?.
load
();
setTimeout
(()
=>
this
.
video
?.
play
(),
100
);
}
else
{
this
.
video
?.
pause
();
}
return
"视频下载"
;
}
@
Watch
(
"value"
)
private
onOpen
()
{
if
(
this
.
value
)
{
this
.
video
?.
load
();
setTimeout
(()
=>
this
.
video
?.
play
(),
100
);
}
else
{
this
.
video
?.
pause
();
}
}
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.preview-title
{
font-size
:
18px
;
color
:
#333
;
margin-bottom
:
15px
;
font-size
:
18px
;
color
:
#333
;
margin-bottom
:
15px
;
}
.actions
{
margin
:
15px
0
;
>
span,
a
{
width
:
30px
;
height
:
30px
;
background-color
:
#7a7b7d
;
color
:
#fff
;
border-radius
:
50%
;
cursor
:
pointer
;
i
{
color
:
#fff
;
font-size
:
20px
;
}
&
+
span
{
margin-left
:
15px
;
}
margin
:
15px
0
;
>
span,
a
{
width
:
30px
;
height
:
30px
;
background-color
:
#7a7b7d
;
color
:
#fff
;
border-radius
:
50%
;
cursor
:
pointer
;
i
{
color
:
#fff
;
font-size
:
20px
;
}
>
a
{
margin-left
:
15px
;
&
+
span
{
margin-left
:
15px
;
}
}
>
a
{
margin-left
:
15px
;
}
}
</
style
>
components/who-read-list.vue
View file @
1f1dc90c
...
...
@@ -138,7 +138,7 @@ export default class WhoReadList extends Vue {
}
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.who-read-list
{
::before
{
content
:
""
;
...
...
create-chat.vue
View file @
1f1dc90c
...
...
@@ -88,4 +88,4 @@ export default class ChatCreator extends Vue {
}
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
></
style
>
<
style
lang=
"
le
ss"
scoped
></
style
>
hybrid-input/index.vue
View file @
1f1dc90c
...
...
@@ -512,7 +512,7 @@ export default class Input extends Vue {
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.input-wrap
{
position
:
relative
;
padding-left
:
20px
;
...
...
message-list.vue
View file @
1f1dc90c
<
template
>
<div
v-loading=
"chatIniting"
class=
"message-list h-100"
>
<!-- chatId:
{{
chatId
}}
-->
<el-scrollbar
ref=
"message-scrollbar"
class=
"
<div
v-loading=
"chatIniting"
class=
"message-list h-100"
>
<!-- chatId:
{{
chatId
}}
-->
<el-scrollbar
ref=
"message-scrollbar"
class=
"
message-list-scrollbar
no-bottom-scrollbar
adjust-el-scroll-right-bar
h-100
"
>
<template
v-for=
"item in messages"
>
<div
:key=
"item.id"
class=
"message-template"
>
<div
v-if=
"
item.id > 0 &&
messageTimestampDictionary[item.id] &&
item.msg
"
class=
"text-center text-hint timestamp"
>
{{
format2Time
(
item
.
ts
)
}}
</div>
<message
:is-sending-message=
"item.id
<
0
"
:failed=
"item.status === -1"
:key=
"item.id"
:data=
"item"
:shape=
"shape"
@
open=
"open"
/>
</div>
</
template
>
</el-scrollbar>
<image-preview
v-model=
"preview"
:file=
"imagePreview"
></image-preview>
<video-preview
v-model=
"previewVideo"
:file=
"videoPreview"
></video-preview>
</div>
>
<template
v-for=
"item in messages"
>
<div
:key=
"item.id"
class=
"message-template"
>
<div
v-if=
"
item.id > 0 && messageTimestampDictionary[item.id] && item.msg
"
class=
"text-center text-hint timestamp"
>
{{
format2Time
(
item
.
ts
)
}}
</div>
<message
:is-sending-message=
"item.id
<
0
"
:failed=
"item.status === -1"
:key=
"item.id"
:data=
"item"
:shape=
"shape"
@
open=
"open"
/>
</div>
</
template
>
</el-scrollbar>
<image-preview
v-model=
"preview"
:file=
"imagePreview"
></image-preview>
<video-preview
v-model=
"previewVideo"
:file=
"videoPreview"
></video-preview>
</div>
</template>
<
script
lang=
"ts"
>
...
...
@@ -56,324 +51,324 @@ import { ChatStore, chatStore } from "@/customer-service/store/model";
@
Component
({
components
:
{
message
,
ImagePreview
,
VideoPreview
}
})
export
default
class
MessageList
extends
Vue
{
@
chatStore
.
Getter
(
ChatStore
.
STATE_CHAT_MSG_HISTORY
)
private
readonly
historyMessage
!
:
ChatStore
.
STATE_CHAT_MSG_HISTORY
@
chatStore
.
Getter
(
ChatStore
.
STATE_CHAT_SENDING_MESSAGES
)
private
readonly
sendingMessages
!
:
ChatStore
.
STATE_CHAT_SENDING_MESSAGES
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
@
chatStore
.
State
(
ChatStore
.
STATE_CURRENT_CHAT_INITING
)
private
readonly
chatIniting
!
:
ChatStore
.
STATE_CURRENT_CHAT_INITING
@
chatStore
.
Action
(
ChatStore
.
ACTION_GET_CHAT_MESSAGES_BEFORE_SPECIFIC_ID
)
private
readonly
getLastPageMsg
!
:
ChatStore
.
ACTION_GET_CHAT_MESSAGES_BEFORE_SPECIFIC_ID
@
chatStore
.
Action
(
ChatStore
.
ACTION_GET_CHAT_MESSAGES_AFTER_SPECIFIC_ID
)
private
readonly
getNextPageMsg
!
:
ChatStore
.
ACTION_GET_CHAT_MESSAGES_AFTER_SPECIFIC_ID
@
chatStore
.
Action
(
ChatStore
.
ACTION_CLEAR_CURRENT_CHAT_DATA
)
private
readonly
clearChatId
!
:
ChatStore
.
ACTION_CLEAR_CURRENT_CHAT_DATA
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM
)
private
readonly
saveScrollToBottomFunc
!
:
ChatStore
.
MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM
)
private
readonly
clearScrollToBottomFunc
!
:
ChatStore
.
MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM
@
Prop
({
default
:
"circle"
})
private
shape
!
:
string
private
get
messages
()
{
if
(
this
.
historyMessage
)
{
if
(
this
.
sendingMessages
)
{
return
[...
this
.
historyMessage
,
...
this
.
sendingMessages
].
filter
(
(
i
)
=>
i
.
chat_id
===
this
.
chatId
);
}
return
this
.
historyMessage
;
}
if
(
this
.
sendingMessages
)
{
return
this
.
sendingMessages
.
filter
((
i
)
=>
i
.
chat_id
===
this
.
chatId
);
}
@
chatStore
.
Getter
(
ChatStore
.
STATE_CHAT_MSG_HISTORY
)
private
readonly
historyMessage
!
:
ChatStore
.
STATE_CHAT_MSG_HISTORY
;
return
[];
}
@
chatStore
.
Getter
(
ChatStore
.
STATE_CHAT_SENDING_MESSAGES
)
private
readonly
sendingMessages
!
:
ChatStore
.
STATE_CHAT_SENDING_MESSAGES
;
// 添加时间戳的最大间隔消息数
private
readonly
timeLimit
=
48
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
private
scroll2EndWhenMessageLoaded
=
false
@
chatStore
.
State
(
ChatStore
.
STATE_CURRENT_CHAT_INITING
)
private
readonly
chatIniting
!
:
ChatStore
.
STATE_CURRENT_CHAT_INITING
;
private
preview
=
false
private
imagePreview
=
{}
@
chatStore
.
Action
(
ChatStore
.
ACTION_GET_CHAT_MESSAGES_BEFORE_SPECIFIC_ID
)
private
readonly
getLastPageMsg
!
:
ChatStore
.
ACTION_GET_CHAT_MESSAGES_BEFORE_SPECIFIC_ID
;
private
previewVideo
=
false
private
videoPreview
=
{}
@
chatStore
.
Action
(
ChatStore
.
ACTION_GET_CHAT_MESSAGES_AFTER_SPECIFIC_ID
)
private
readonly
getNextPageMsg
!
:
ChatStore
.
ACTION_GET_CHAT_MESSAGES_AFTER_SPECIFIC_ID
;
@
Ref
(
"message-scrollbar"
)
private
scollbarElement
!
:
Vue
&
{
update
:
()
=>
void
;
}
@
chatStore
.
Action
(
ChatStore
.
ACTION_CLEAR_CURRENT_CHAT_DATA
)
private
readonly
clearChatId
!
:
ChatStore
.
ACTION_CLEAR_CURRENT_CHAT_DATA
;
private
get
scollWrapper
():
HTMLElement
|
null
{
return
this
.
scollbarElement
?.
$el
?.
firstChild
as
HTMLElement
;
}
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM
)
private
readonly
saveScrollToBottomFunc
!
:
ChatStore
.
MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM
;
@
Watch
(
"messages"
)
private
whenHasMessages
()
{
this
.
$nextTick
(()
=>
this
.
scollbarElement
.
update
());
}
@
Watch
(
"preview"
)
private
onPreviewChanged
()
{
if
(
!
this
.
preview
)
{
this
.
raiseFileOpen
(
false
);
}
}
@
Watch
(
"previewVideo"
)
private
onVideoPreviewChanged
()
{
if
(
!
this
.
previewVideo
)
{
this
.
raiseFileOpen
(
false
);
}
}
private
raiseFileOpen
(
value
:
boolean
)
{
this
.
$emit
(
"file-open"
,
value
);
}
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM
)
private
readonly
clearScrollToBottomFunc
!
:
ChatStore
.
MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM
;
private
get
messageTimestampDictionary
()
{
const
dic
=
{}
as
{
[
prop
:
number
]:
boolean
};
let
count
=
0
;
if
(
this
.
historyMessage
)
{
this
.
historyMessage
.
forEach
((
message
,
index
,
array
)
=>
{
if
(
index
===
0
||
this
.
whetherShowTime
(
array
[
index
-
1
],
message
)
||
count
===
this
.
timeLimit
-
1
)
{
dic
[
message
.
id
]
=
true
;
count
=
0
;
}
else
{
count
++
;
}
});
}
@
Prop
({
default
:
"circle"
})
private
shape
!
:
string
;
return
dic
;
private
get
messages
()
{
if
(
this
.
historyMessage
)
{
if
(
this
.
sendingMessages
)
{
return
[...
this
.
historyMessage
,
...
this
.
sendingMessages
].
filter
(
(
i
)
=>
i
.
chat_id
===
this
.
chatId
);
}
return
this
.
historyMessage
;
}
private
loading
=
false
private
loadingOld
=
false
private
loadingNew
=
false
public
created
()
{
this
.
handleScrollWrapper
();
if
(
this
.
sendingMessages
)
{
return
this
.
sendingMessages
.
filter
((
i
)
=>
i
.
chat_id
===
this
.
chatId
);
}
public
mounted
()
{
this
.
scollWrapper
&&
this
.
scollWrapper
.
addEventListener
(
"scroll"
,
this
.
handleScroll
);
this
.
saveScrollToBottomFunc
(
this
.
scrollToNewMsg
);
}
return
[];
}
public
beforeDestroy
()
{
this
.
scollWrapper
&&
this
.
scollWrapper
.
removeEventListener
(
"scroll"
,
this
.
handleScroll
);
this
.
clearScrollToBottomFunc
();
this
.
clearChatId
();
}
// 添加时间戳的最大间隔消息数
private
readonly
timeLimit
=
48
;
public
scroll2End
(
delay
?:
number
)
{
this
.
$nextTick
(()
=>
{
const
wrap
=
this
.
scollbarElement
?.
$el
.
querySelector
(
".el-scrollbar__wrap"
)
as
HTMLElement
;
if
(
wrap
)
{
if
(
delay
)
{
setTimeout
(()
=>
{
wrap
.
scrollTop
=
10000
;
},
delay
);
return
;
}
wrap
.
scrollTop
=
10000
;
}
});
}
private
scroll2EndWhenMessageLoaded
=
false
;
private
startLoading
()
{
this
.
loading
=
true
;
}
private
preview
=
false
;
private
imagePreview
=
{};
private
endLoading
()
{
this
.
loading
=
false
;
}
private
previewVideo
=
false
;
private
videoPreview
=
{};
private
startLoadingOld
()
{
this
.
startLoading
();
this
.
loadingOld
=
true
;
}
@
Ref
(
"message-scrollbar"
)
private
scollbarElement
!
:
Vue
&
{
update
:
()
=>
void
;
};
private
endLoadingOld
()
{
this
.
endLoading
();
this
.
loadingOld
=
false
;
}
private
get
scollWrapper
():
HTMLElement
|
null
{
return
this
.
scollbarElement
?.
$el
?.
firstChild
as
HTMLElement
;
}
private
startLoadingNew
()
{
this
.
startLoading
();
this
.
loadingNew
=
true
;
}
@
Watch
(
"messages"
)
private
whenHasMessages
()
{
this
.
$nextTick
(()
=>
this
.
scollbarElement
.
update
())
;
}
private
endLoadingNew
()
{
this
.
endLoading
();
this
.
loadingNew
=
false
;
@
Watch
(
"preview"
)
private
onPreviewChanged
()
{
if
(
!
this
.
preview
)
{
this
.
raiseFileOpen
(
false
);
}
}
private
handleScroll
!
:
()
=>
void
private
handleScrollWrapper
()
{
let
oldScrollTop
=
0
;
this
.
handleScroll
=
()
=>
{
const
wrapper
=
this
.
scollWrapper
;
const
gap
=
150
;
if
(
wrapper
==
null
)
return
;
const
view
=
wrapper
.
firstChild
as
HTMLElement
;
const
wrapperH
=
wrapper
.
getBoundingClientRect
().
height
;
const
viewH
=
view
.
getBoundingClientRect
().
height
;
let
scrollUp
=
false
;
let
scrollDown
=
false
;
if
(
oldScrollTop
>
wrapper
.
scrollTop
)
{
scrollUp
=
true
;
scrollDown
=
false
;
}
else
if
(
oldScrollTop
<
wrapper
.
scrollTop
)
{
scrollUp
=
false
;
scrollDown
=
true
;
}
this
.
forbidScrollTopToZero
(
wrapper
);
if
(
wrapper
.
scrollTop
<=
gap
)
{
scrollUp
&&
this
.
fetchOldMsg
();
}
if
(
wrapper
.
scrollTop
-
40
-
(
viewH
-
wrapperH
)
>=
-
gap
)
{
scrollDown
&&
this
.
fetchNewMsg
();
}
oldScrollTop
=
wrapper
.
scrollTop
;
};
@
Watch
(
"previewVideo"
)
private
onVideoPreviewChanged
()
{
if
(
!
this
.
previewVideo
)
{
this
.
raiseFileOpen
(
false
);
}
/* scrollTop为0时,新加载的消息后,滚动条也会保持在0的位置 */
private
forbidScrollTopToZero
(
ele
:
HTMLElement
)
{
if
(
ele
.
scrollTop
<=
10
)
{
ele
.
scrollTop
=
10
;
}
private
raiseFileOpen
(
value
:
boolean
)
{
this
.
$emit
(
"file-open"
,
value
);
}
private
get
messageTimestampDictionary
()
{
const
dic
=
{}
as
{
[
prop
:
number
]:
boolean
};
let
count
=
0
;
if
(
this
.
historyMessage
)
{
this
.
historyMessage
.
forEach
((
message
,
index
,
array
)
=>
{
if
(
index
===
0
||
this
.
whetherShowTime
(
array
[
index
-
1
],
message
)
||
count
===
this
.
timeLimit
-
1
)
{
dic
[
message
.
id
]
=
true
;
count
=
0
;
}
else
{
count
++
;
}
});
}
private
scrollToNewMsg
()
{
this
.
$nextTick
(()
=>
{
if
(
this
.
loading
)
return
;
this
.
scroll2End
();
});
}
@
throttle
()
private
async
fetchOldMsg
()
{
if
(
this
.
loading
)
return
;
const
msg
=
this
.
historyMessage
;
if
(
msg
==
null
)
return
;
if
(
msg
.
length
===
0
)
return
;
this
.
startLoadingOld
();
const
msgId
=
msg
[
0
].
id
;
const
data
=
await
this
.
getLastPageMsg
(
msgId
);
if
(
data
.
length
===
0
)
{
// eslint-disable-next-line no-console
console
.
log
(
"没有更多老消息了"
);
return
dic
;
}
private
loading
=
false
;
private
loadingOld
=
false
;
private
loadingNew
=
false
;
public
created
()
{
this
.
handleScrollWrapper
();
}
public
mounted
()
{
this
.
scollWrapper
&&
this
.
scollWrapper
.
addEventListener
(
"scroll"
,
this
.
handleScroll
);
this
.
saveScrollToBottomFunc
(
this
.
scrollToNewMsg
);
}
public
beforeDestroy
()
{
this
.
scollWrapper
&&
this
.
scollWrapper
.
removeEventListener
(
"scroll"
,
this
.
handleScroll
);
this
.
clearScrollToBottomFunc
();
this
.
clearChatId
();
}
public
scroll2End
(
delay
?:
number
)
{
this
.
$nextTick
(()
=>
{
const
wrap
=
this
.
scollbarElement
?.
$el
.
querySelector
(
".el-scrollbar__wrap"
)
as
HTMLElement
;
if
(
wrap
)
{
if
(
delay
)
{
setTimeout
(()
=>
{
wrap
.
scrollTop
=
10000
;
},
delay
);
return
;
}
this
.
$emit
(
"last-page"
,
msgId
);
this
.
endLoadingOld
();
wrap
.
scrollTop
=
10000
;
}
});
}
private
startLoading
()
{
this
.
loading
=
true
;
}
private
endLoading
()
{
this
.
loading
=
false
;
}
private
startLoadingOld
()
{
this
.
startLoading
();
this
.
loadingOld
=
true
;
}
private
endLoadingOld
()
{
this
.
endLoading
();
this
.
loadingOld
=
false
;
}
private
startLoadingNew
()
{
this
.
startLoading
();
this
.
loadingNew
=
true
;
}
private
endLoadingNew
()
{
this
.
endLoading
();
this
.
loadingNew
=
false
;
}
private
handleScroll
!
:
()
=>
void
;
private
handleScrollWrapper
()
{
let
oldScrollTop
=
0
;
this
.
handleScroll
=
()
=>
{
const
wrapper
=
this
.
scollWrapper
;
const
gap
=
150
;
if
(
wrapper
==
null
)
return
;
const
view
=
wrapper
.
firstChild
as
HTMLElement
;
const
wrapperH
=
wrapper
.
getBoundingClientRect
().
height
;
const
viewH
=
view
.
getBoundingClientRect
().
height
;
let
scrollUp
=
false
;
let
scrollDown
=
false
;
if
(
oldScrollTop
>
wrapper
.
scrollTop
)
{
scrollUp
=
true
;
scrollDown
=
false
;
}
else
if
(
oldScrollTop
<
wrapper
.
scrollTop
)
{
scrollUp
=
false
;
scrollDown
=
true
;
}
this
.
forbidScrollTopToZero
(
wrapper
);
if
(
wrapper
.
scrollTop
<=
gap
)
{
scrollUp
&&
this
.
fetchOldMsg
();
}
if
(
wrapper
.
scrollTop
-
40
-
(
viewH
-
wrapperH
)
>=
-
gap
)
{
scrollDown
&&
this
.
fetchNewMsg
();
}
oldScrollTop
=
wrapper
.
scrollTop
;
};
}
/* scrollTop为0时,新加载的消息后,滚动条也会保持在0的位置 */
private
forbidScrollTopToZero
(
ele
:
HTMLElement
)
{
if
(
ele
.
scrollTop
<=
10
)
{
ele
.
scrollTop
=
10
;
}
@
throttle
()
private
async
fetchNewMsg
()
{
if
(
this
.
loading
)
return
;
const
msg
=
this
.
historyMessage
;
if
(
msg
==
null
)
return
;
if
(
msg
.
length
===
0
)
return
;
this
.
startLoadingNew
();
const
msgId
=
msg
[
msg
.
length
-
1
].
id
;
const
data
=
await
this
.
getNextPageMsg
(
msgId
);
if
(
data
.
length
===
0
)
{
// eslint-disable-next-line no-console
console
.
log
(
"没有更多新消息了"
);
}
this
.
$emit
(
"next-page"
,
msgId
);
this
.
endLoadingNew
();
}
private
scrollToNewMsg
()
{
this
.
$nextTick
(()
=>
{
if
(
this
.
loading
)
return
;
this
.
scroll2End
();
});
}
@
throttle
()
private
async
fetchOldMsg
()
{
if
(
this
.
loading
)
return
;
const
msg
=
this
.
historyMessage
;
if
(
msg
==
null
)
return
;
if
(
msg
.
length
===
0
)
return
;
this
.
startLoadingOld
();
const
msgId
=
msg
[
0
].
id
;
const
data
=
await
this
.
getLastPageMsg
(
msgId
);
if
(
data
.
length
===
0
)
{
// eslint-disable-next-line no-console
console
.
log
(
"没有更多老消息了"
);
}
private
format2Time
(
time
:
number
)
{
return
formatTime
(
time
);
this
.
$emit
(
"last-page"
,
msgId
);
this
.
endLoadingOld
();
}
@
throttle
()
private
async
fetchNewMsg
()
{
if
(
this
.
loading
)
return
;
const
msg
=
this
.
historyMessage
;
if
(
msg
==
null
)
return
;
if
(
msg
.
length
===
0
)
return
;
this
.
startLoadingNew
();
const
msgId
=
msg
[
msg
.
length
-
1
].
id
;
const
data
=
await
this
.
getNextPageMsg
(
msgId
);
if
(
data
.
length
===
0
)
{
// eslint-disable-next-line no-console
console
.
log
(
"没有更多新消息了"
);
}
private
whetherShowTime
(
previous
:
Message
,
current
:
Message
)
{
return
current
.
ts
-
previous
.
ts
>
180
;
this
.
$emit
(
"next-page"
,
msgId
);
this
.
endLoadingNew
();
}
private
format2Time
(
time
:
number
)
{
return
formatTime
(
time
);
}
private
whetherShowTime
(
previous
:
Message
,
current
:
Message
)
{
return
current
.
ts
-
previous
.
ts
>
180
;
}
private
open
(
file
:
{
type
:
string
;
msg
:
{
url
:
string
;
name
:
string
;
size
:
number
};
})
{
if
(
file
.
type
===
"image"
)
{
this
.
imagePreview
=
file
.
msg
;
this
.
preview
=
true
;
return
this
.
raiseFileOpen
(
true
);
}
private
open
(
file
:
{
type
:
string
;
msg
:
{
url
:
string
;
name
:
string
;
size
:
number
};
})
{
if
(
file
.
type
===
"image"
)
{
this
.
imagePreview
=
file
.
msg
;
this
.
preview
=
true
;
return
this
.
raiseFileOpen
(
true
);
}
if
(
file
.
type
===
"video"
)
{
this
.
videoPreview
=
file
.
msg
;
this
.
previewVideo
=
true
;
return
this
.
raiseFileOpen
(
true
);
}
if
(
file
.
type
===
"video"
)
{
this
.
videoPreview
=
file
.
msg
;
this
.
previewVideo
=
true
;
return
this
.
raiseFileOpen
(
true
);
}
/**
* 获取当期消息列表头尾消息的id
*/
public
getStart2EndMessageIds
()
{
const
v
:
{
start
:
number
;
end
:
number
}
=
{
start
:
0
,
end
:
0
};
if
(
this
.
historyMessage
&&
this
.
historyMessage
.
length
)
{
const
start
=
this
.
historyMessage
[
0
];
v
.
start
=
start
.
id
;
const
end
=
this
.
historyMessage
[
this
.
historyMessage
.
length
-
1
];
v
.
end
=
end
.
id
;
}
return
v
;
}
/**
* 获取当期消息列表头尾消息的id
*/
public
getStart2EndMessageIds
()
{
const
v
:
{
start
:
number
;
end
:
number
}
=
{
start
:
0
,
end
:
0
};
if
(
this
.
historyMessage
&&
this
.
historyMessage
.
length
)
{
const
start
=
this
.
historyMessage
[
0
];
v
.
start
=
start
.
id
;
const
end
=
this
.
historyMessage
[
this
.
historyMessage
.
length
-
1
];
v
.
end
=
end
.
id
;
}
return
v
;
}
}
</
script
>
<
style
lang=
"
sc
ss"
scoped
>
<
style
lang=
"
le
ss"
scoped
>
.message-list
{
padding
:
0
20px
;
padding
:
0
20px
;
}
.loading-mask
{
height
:
50px
;
line-height
:
50px
;
text-align
:
center
;
height
:
50px
;
line-height
:
50px
;
text-align
:
center
;
}
.message-template
{
&:first-child
{
.timestamp
{
margin-top
:
20px
;
}
}
&:first-child
{
.timestamp
{
font-size
:
12px
;
user-select
:
none
;
margin-top
:
20px
;
}
}
.timestamp
{
font-size
:
12px
;
user-select
:
none
;
}
}
</
style
>
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