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
2634302f
authored
Jan 13, 2022
by
杨铁龙
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge commit '
2155b785
' into wx_master
parents
51763f55
2155b785
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
551 additions
and
348 deletions
components/chat-room.vue
components/controller/chat-list.ts
components/image-preview.vue
components/message-input.vue
components/message-item/audio-message.vue
components/message-item/position-message.vue
components/message-list.vue
components/message.vue
database/dev-tools.ts
database/index.ts
hybrid-input/index.vue
model/im.ts
model/index.ts
service/monitor.ts
service/order.ts
store/index.ts
store/model.ts
utils/chat-info.ts
utils/index.ts
utils/user-info.ts
xim/index.ts
xim/models/chat.ts
xim/xim.ts
components/chat-room.vue
View file @
2634302f
<
template
>
<div
class=
"chat-room-con
h-100
pos-rel"
>
<div
class=
"chat-room-con pos-rel"
>
<div
class=
"chat-panel"
>
<div
class=
"chat-area h-100 d-flex flex-column"
ref=
"chatBox"
>
<div
...
...
@@ -43,25 +43,22 @@
import
Chat
from
"@/customer-service/xim"
;
import
{
CustomerServiceEvent
}
from
"../event"
;
const
chatResizeKey1
=
"chat-resize-1"
;
const
chatResizeKey2
=
"chat-resize-2"
;
@
Component
({
components
:
{
MessageInput
,
messages
}
})
export
default
class
ChatRoom
extends
Vue
{
@
Ref
(
"chatBox"
)
private
readonly
chatBox
!
:
Element
;
@
Ref
(
"top"
)
private
readonly
refTop
!
:
Element
;
@
Ref
(
"bottom"
)
private
readonly
refBottom
!
:
Element
;
@
Ref
(
"resize"
)
private
readonly
refResize
!
:
Element
;
@
Ref
(
"chatBox"
)
private
readonly
chatBox
!
:
HTML
Element
;
@
Ref
(
"top"
)
private
readonly
refTop
!
:
HTML
Element
;
@
Ref
(
"bottom"
)
private
readonly
refBottom
!
:
HTML
Element
;
@
Ref
(
"resize"
)
private
readonly
refResize
!
:
HTML
Element
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_CLEAR_CURRENT_CHAT_MEMBERS
)
private
readonly
clearChatMembers
!
:
ChatStore
.
MUTATION_CLEAR_CURRENT_CHAT_MEMBERS
;
@
chatStore
.
State
(
ChatStore
.
STATE_CURRENT_CHAT_INPUTING
)
private
readonly
currentInputPeople
!
:
ChatStore
.
STATE_CURRENT_CHAT_INPUTING
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
)
private
readonly
currentChatUniplatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_UNIPLAT_ID
;
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
)
private
readonly
isChatMember
!
:
ChatStore
.
STATE_CHAT_CURRENT_IS_CHAT_MEMBER
;
...
...
@@ -79,12 +76,6 @@
@
Provide
()
showReadSummary
=
true
;
@
Watch
(
"currentChatUniplatId"
)
private
whenCurrentChatIdChanged
(
newValue
:
string
,
oldValue
:
string
)
{
if
(
Number
(
oldValue
)
===
Number
(
newValue
))
return
;
this
.
clearChatMembers
();
}
private
get
getCurrentInputingPeople
()
{
return
this
.
currentInputPeople
.
map
(()
=>
""
/* this.userInfo[k].name */
)
...
...
@@ -97,9 +88,9 @@
private
dragControllerDiv
(
e
:
MouseEvent
)
{
const
resize
=
this
.
refResize
as
any
;
const
top
=
this
.
refTop
as
HTMLElement
;
const
bottom
=
this
.
refBottom
as
HTMLElement
;
const
box
=
this
.
chatBox
as
HTMLElement
;
const
top
=
this
.
refTop
;
const
bottom
=
this
.
refBottom
;
const
box
=
this
.
chatBox
;
const
startY
=
e
.
clientY
;
const
originTop
=
resize
.
offsetTop
;
...
...
@@ -115,6 +106,9 @@
resize
.
style
.
top
=
moveLen
+
"px"
;
// 设置左侧区域的宽度
top
.
style
.
height
=
moveLen
+
"px"
;
bottom
.
style
.
height
=
bottomHeight
+
"px"
;
localStorage
.
setItem
(
chatResizeKey1
,
moveLen
);
localStorage
.
setItem
(
chatResizeKey2
,
bottomHeight
+
""
);
};
document
.
onmouseup
=
function
()
{
document
.
onmousemove
=
null
;
...
...
@@ -126,9 +120,29 @@
}
mounted
()
{
this
.
refBottom
&&
((
this
.
refBottom
as
HTMLElement
).
style
.
height
=
this
.
chatBox
.
clientHeight
-
this
.
refTop
.
clientHeight
+
"px"
);
this
.
adjust
();
}
private
adjust
()
{
setTimeout
(()
=>
{
if
(
localStorage
.
getItem
(
chatResizeKey1
)
&&
localStorage
.
getItem
(
chatResizeKey2
)
&&
this
.
hasInput
)
{
const
s1
=
localStorage
.
getItem
(
chatResizeKey1
)
+
"px"
;
const
s2
=
localStorage
.
getItem
(
chatResizeKey2
)
+
"px"
;
this
.
refTop
&&
(
this
.
refTop
.
style
.
height
=
s1
);
this
.
refResize
&&
(
this
.
refResize
.
style
.
top
=
s1
);
this
.
refBottom
&&
(
this
.
refBottom
.
style
.
height
=
s2
);
}
else
{
this
.
refBottom
&&
((
this
.
refBottom
as
HTMLElement
).
style
.
height
=
this
.
chatBox
.
clientHeight
-
this
.
refTop
.
clientHeight
+
"px"
);
}
},
800
);
}
private
openMessage
(
o
:
any
)
{
...
...
@@ -148,6 +162,7 @@
),
300
);
this
.
$emit
(
"send"
);
}
}
</
script
>
...
...
@@ -158,7 +173,7 @@
width
:
46px
;
height
:
20px
;
line-height
:
20px
;
background
:
#22bd7a
;
background
-color
:
#22bd7a
;
font-size
:
13px
;
border-radius
:
2px
;
color
:
#ffffff
;
...
...
@@ -223,6 +238,10 @@
top
:
calc
(
100%
-
130px
+
1px
);
height
:
6px
;
width
:
100%
;
&:hover
{
background-color
:
#eee
;
}
}
}
.order-info-con
{
...
...
components/controller/chat-list.ts
View file @
2634302f
...
...
@@ -7,6 +7,8 @@ import { ChatUserInfoService } from "@/customer-service/utils/user-info";
@
Component
({
components
:
{}
})
export
default
class
ChatList
extends
Vue
{
private
nextTimer
=
0
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_GET_MY_CHAT_LIST
)
protected
readonly
getMyChatList
!
:
ChatStore
.
ACTION_GET_MY_CHAT_LIST
;
...
...
@@ -91,4 +93,18 @@ export default class ChatList extends Vue {
}
return
this
.
parseMesage
(
item
);
}
/**
* 一分钟更新一次会话列表
*/
protected
enableAutoRefresh
()
{
this
.
nextTimer
=
setTimeout
(
()
=>
this
.
getMyChatList
().
finally
(()
=>
this
.
enableAutoRefresh
()),
60
*
1000
);
}
beforeDestroy
()
{
clearTimeout
(
this
.
nextTimer
);
}
}
components/image-preview.vue
View file @
2634302f
...
...
@@ -3,21 +3,17 @@
:modal=
"false"
:before-close=
"close"
:visible=
"value"
custom-class=
"hide-header show-close padding-0 width-auto"
:show-close=
"false"
custom-class=
"transparent"
width=
"90%"
>
<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
class=
"d-flex justify-content-center align-items-start"
>
<img
v-if=
"file"
:src=
"file.url"
/>
<i
class=
"el-icon-close"
@
click=
"close"
></i>
</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"
...
...
@@ -31,77 +27,68 @@
</
template
>
<
script
lang=
"ts"
>
import
{
Component
,
Model
,
Prop
,
Vue
}
from
"vue-property-decorator"
;
@
Component
({
components
:
{}
})
export
default
class
ImagePreview
extends
Vue
{
@
Model
(
"update"
)
private
value
!
:
boolean
;
import
{
Component
,
Model
,
Prop
,
Vue
}
from
"vue-property-decorator"
;
@
Prop
()
private
file
!
:
{
name
:
string
;
url
:
string
};
@
Component
({
components
:
{}
})
export
default
class
ImagePreview
extends
Vue
{
@
Model
(
"update"
)
private
value
!
:
boolean
;
private
style
:
{
"max-height"
:
number
|
string
;
"max-width"
:
number
|
string
;
}
=
{
"max-height"
:
"300px"
,
"max-width"
:
"600px"
,
};
@
Prop
()
private
file
!
:
{
name
:
string
;
url
:
string
};
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
close
()
{
this
.
$emit
(
"update"
,
false
);
}
private
get
getAttachment
()
{
if
(
this
.
file
)
{
return
this
.
file
.
name
;
private
get
getAttachment
()
{
if
(
this
.
file
)
{
return
this
.
file
.
name
;
}
return
"文件下载"
;
}
return
"文件下载"
;
}
}
</
script
>
<
style
lang=
"less"
scoped
>
.preview-title
{
font-size
:
18px
;
color
:
#333
;
margin-bottom
:
15px
;
}
img
{
max-width
:
100%
;
max-height
:
100%
;
.actions
{
margin
:
15px
0
;
&
+
i
{
top
:
-25px
;
right
:
20px
;
background-color
:
rgba
(
0
,
0
,
0
,
0.5
);
border-radius
:
50%
;
padding
:
5px
;
font-size
:
40px
;
color
:
#fff
;
display
:
table
;
position
:
relative
;
z-index
:
1
;
cursor
:
pointer
;
}
}
>
span,
a
{
width
:
30px
;
height
:
30px
;
background-color
:
#7a7b7d
;
color
:
#fff
;
border-radius
:
50%
;
cursor
:
pointer
;
.actions
{
margin
:
15px
0
;
i
{
a
{
width
:
50px
;
height
:
50px
;
background-color
:
#7a7b7d
;
color
:
#fff
;
font-size
:
20px
;
}
border-radius
:
50%
;
cursor
:
pointer
;
&
+
span
{
margin-left
:
15px
;
}
}
i
{
color
:
#fff
;
font-size
:
30px
;
}
>
a
{
margin-left
:
15px
;
&
+
span
{
margin-left
:
15px
;
}
}
}
}
</
style
>
components/message-input.vue
View file @
2634302f
...
...
@@ -9,7 +9,7 @@
<
script
lang=
"ts"
>
import
{
Component
,
Ref
,
Vue
,
Watch
}
from
"vue-property-decorator"
;
import
ChatInput
,
{
FILE_INFO_CLASS
,
isFileElement
,
isImageOrFile
,
}
from
"../hybrid-input/index.vue"
;
import
{
Message
,
MessageType
}
from
"../model"
;
...
...
@@ -51,6 +51,8 @@
@
Ref
(
"chat-input"
)
private
readonly
chatInput
!
:
ChatInput
;
private
sending
=
false
;
@
Watch
(
"chatRoomVisible"
)
private
whenChatRoomShow
()
{
if
(
!
this
.
chatRoomVisible
)
return
;
...
...
@@ -58,27 +60,39 @@
}
private
async
sendMessage
(
msg
:
ChildNode
[])
{
if
(
this
.
chatIniting
)
{
if
(
this
.
chatIniting
||
this
.
sending
)
{
return
;
}
const
count
=
msg
.
length
;
let
finished
=
0
;
this
.
sending
=
true
;
const
onFinishedChanged
=
()
=>
{
finished
++
;
if
(
finished
===
count
)
{
this
.
sending
=
false
;
}
};
setTimeout
(()
=>
(
this
.
sending
=
false
),
3000
);
for
(
const
item
of
msg
)
{
if
(
isImageOrFile
(
item
))
{
if
(
(
item
as
Element
).
classList
.
contains
(
FILE_INFO_CLASS
))
{
await
this
.
sendFile
(
item
,
MessageType
.
File
)
.
catch
((
e
)
=>
this
.
onError
(
e
)
);
if
(
isFileElement
(
item
))
{
await
this
.
sendFile
(
item
,
MessageType
.
File
)
.
catch
((
e
)
=>
this
.
onError
(
e
)
)
.
finally
(
onFinishedChanged
);
}
else
{
await
this
.
sendFile
(
item
,
MessageType
.
Image
)
.
catch
((
e
)
=>
this
.
onError
(
e
)
);
await
this
.
sendFile
(
item
,
MessageType
.
Image
)
.
catch
((
e
)
=>
this
.
onError
(
e
)
)
.
finally
(
onFinishedChanged
);
}
continue
;
}
if
(
item
.
textContent
)
{
await
this
.
sendText
(
item
.
textContent
).
catch
((
e
)
=>
this
.
onError
(
e
)
);
await
this
.
sendText
(
item
.
textContent
)
.
catch
((
e
)
=>
this
.
onError
(
e
))
.
finally
(
onFinishedChanged
);
}
else
{
onFinishedChanged
();
}
}
this
.
$emit
(
"sent"
);
...
...
@@ -166,7 +180,8 @@
console
.
error
(
e
);
this
.
setMsg2Failed
(
index
);
this
.
chatInput
&&
this
.
chatInput
.
updateUploadProgress
(
0
);
this
.
chatInput
&&
this
.
chatInput
.
updateUploadProgress
(
0
);
});
}
}
...
...
components/message-item/audio-message.vue
View file @
2634302f
<
template
>
<div
class=
"msg-detail voice-message d-flex align-items-center"
:class=
"
{ playing: playing, 'can-play': messageRealUrl }"
@click.stop="play"
:style="{ width: getVoiceMessageWidth + 'px' }"
>
<div
class=
"d-flex align-items-center"
v-if=
"messageRealUrl"
>
<voice-icon
:loading=
"playing"
></voice-icon>
<audio
ref=
"audio"
@
play=
"onPlay"
@
pause=
"onPause"
>
<source
type=
"audio/aac"
:src=
"messageRealUrl"
/>
</audio>
<span
v-if=
"duration"
class=
"duration text-nowrap text-hint"
>
{{
durationInSecond
}}
s
</span
>
<div>
<div
class=
"msg-detail voice-message d-flex align-items-center"
:class=
"
{ playing: playing, 'can-play': messageRealUrl }"
@click.stop="play"
:style="{ width: getVoiceMessageWidth + 'px' }"
>
<div
class=
"d-flex align-items-center"
v-if=
"messageRealUrl"
>
<voice-icon
:loading=
"playing"
></voice-icon>
<audio
ref=
"audio"
@
play=
"onPlay"
@
pause=
"onPause"
>
<source
type=
"audio/aac"
:src=
"messageRealUrl"
/>
</audio>
<span
v-if=
"duration"
class=
"duration text-nowrap text-hint"
>
{{
durationInSecond
}}
s
</span
>
</div>
<i
class=
"el-icon-warning-outline"
v-else-if=
"fileFailed2Load"
title=
"[语音加载失败]"
></i>
</div>
<i
class=
"el-icon-warning-outline"
v-else-if=
"fileFailed2Load"
title=
"[语音加载失败]"
></i>
<text-message
v-model=
"value"
v-if=
"backend"
/>
</div>
</
template
>
...
...
@@ -92,6 +93,9 @@
<
style
lang=
"less"
scoped
>
.voice-message
{
width
:
200px
;
background-color
:
#eee
;
border-radius
:
6px
;
padding
:
8px
10px
;
&.can-play
{
cursor
:
pointer
;
...
...
@@ -102,12 +106,6 @@
}
}
.inline-text
{
position
:
absolute
;
bottom
:
0
;
left
:
40px
;
}
.my-message
{
.voice-message
{
>
div
{
...
...
components/message-item/position-message.vue
View file @
2634302f
<
template
>
<div
class=
"position-message"
@
click=
"openPosition"
>
<div
class=
"d-flex justify-content-between align-items-center"
>
<span
class=
"d-flex align-items-center"
>
<span
class=
"title"
>
{{
title
}}
</span>
<span
class=
"d-flex align-items-center
flex-fill
"
>
<span
class=
"title
flex-fill
"
>
{{
title
}}
</span>
<span
v-for=
"item in tags"
:key=
"item.title"
...
...
@@ -46,7 +46,43 @@
}
private
get
salary
()
{
return
this
.
positionData
.
salary
;
const
max
=
this
.
positionData
.
max_salary
;
const
min
=
this
.
positionData
.
min_salary
;
const
formatSalary
=
(
v
:
number
,
type
?:
"Y"
|
"K"
)
=>
{
if
(
type
===
"K"
)
{
return
{
v
:
parseFloat
((
v
/
1000
).
toFixed
(
2
)),
unit
:
"K"
,
};
}
if
(
+
v
<
1000
)
{
return
{
v
,
unit
:
""
,
};
}
else
{
return
{
v
:
parseFloat
((
v
/
1000
).
toFixed
(
2
)),
unit
:
"K"
,
};
}
};
if
(
!
max
&&
!
min
)
{
return
"面议"
;
}
if
(
+
min
>=
+
max
)
{
const
v
=
formatSalary
(
min
);
return
`
${
v
.
v
}${
v
.
unit
}
`
;
}
const
formatMin
=
formatSalary
(
min
);
const
formatMax
=
formatSalary
(
max
);
if
(
formatMin
.
unit
===
formatMax
.
unit
)
{
return
`
${
formatMin
.
v
}
-
${
formatMax
.
v
}
${
formatMax
.
unit
}
`
;
}
else
{
const
formatMin
=
formatSalary
(
min
,
"K"
);
const
formatMax
=
formatSalary
(
max
,
"K"
);
return
`
${
formatMin
.
v
}
-
${
formatMax
.
v
}
K`
;
}
}
private
get
positionBody
()
{
...
...
@@ -64,7 +100,9 @@
private
get
tail
()
{
return
[
this
.
positionData
.
company_name
,
this
.
positionData
.
business_scope
,
this
.
positionData
.
business_scope
!==
'0'
?
this
.
positionData
.
business_scope
:
""
,
];
}
...
...
components/message-list.vue
View file @
2634302f
...
...
@@ -50,7 +50,6 @@
import
{
dbController
}
from
"../database"
;
import
{
getLastMessageId
}
from
"../store"
;
import
{
CustomerServiceEvent
}
from
"../event"
;
import
xim
from
"../xim/xim"
;
@
Component
({
components
:
{
message
,
ImagePreview
,
VideoPreview
}
})
export
default
class
MessageList
extends
Vue
{
...
...
@@ -95,7 +94,7 @@
private
get
messages
()
{
if
(
this
.
historyMessage
)
{
if
(
this
.
sendingMessages
)
{
if
(
this
.
sendingMessages
&&
this
.
sendingMessages
.
length
)
{
return
[...
this
.
historyMessage
,
...
this
.
sendingMessages
].
filter
(
(
i
)
=>
i
.
chat_id
===
this
.
chatId
&&
i
.
id
>
0
);
...
...
@@ -103,7 +102,7 @@
return
this
.
historyMessage
;
}
if
(
this
.
sendingMessages
)
{
if
(
this
.
sendingMessages
&&
this
.
sendingMessages
.
length
)
{
return
this
.
sendingMessages
.
filter
(
(
i
)
=>
i
.
chat_id
===
this
.
chatId
&&
i
.
id
>
0
);
...
...
@@ -190,15 +189,6 @@
public
created
()
{
this
.
handleScrollWrapper
();
this
.
onNewMessage
((
e
)
=>
{
if
(
e
.
type
===
MessageType
.
Withdraw
)
{
const
ids
=
xim
.
withDrawMsgHandle
(
e
);
this
.
executeWithDraw
(
ids
);
dbController
.
removeMessage
(
e
.
chat_id
,
ids
)
.
finally
(()
=>
this
.
refresh
());
}
});
}
public
mounted
()
{
...
...
components/message.vue
View file @
2634302f
...
...
@@ -33,10 +33,17 @@
<div
v-if=
"isMyMessage"
class=
"msg-read pos-rel"
>
<span
@
click=
"openReaderList"
class=
"pointer"
:class=
"
{ all: isAllRead }"
:class=
"[
isAllRead ? 'all' : 'not-all',
{ pointer: isChatMember },
]"
>
<template
v-if=
"isAllRead"
>
全部已读
</
template
>
<template
v-if=
"isAllRead"
>
<i
class=
"el-icon-circle-check"
title=
"全部已读"
></i>
</
template
>
<
template
v-else-if=
"manualReaded || data.read_count"
>
{{
...
...
@@ -73,12 +80,7 @@
/>
<avatar
v-if=
"!isQuestionAnswerMessage && !isWithdrawMessage"
:src=
"
chatRole === 'admin' ||
chatRole === 'customer-service'
? defaultAvatar
: avatar
"
:src=
"avatar || defaultAvatar"
shape=
"circle"
/>
</div>
...
...
@@ -153,7 +155,6 @@
import
avatar
from
"@/customer-service/components/avatar.vue"
;
import
{
chatStore
,
ChatStore
}
from
"@/customer-service/store/model"
;
import
ximInstance
from
"../xim/xim"
;
import
{
dbController
}
from
"../database"
;
import
ImageMessage
from
"./message-item/image-message.vue"
;
import
FileMessage
from
"./message-item/file-message.vue"
;
import
AudioMessage
from
"./message-item/audio-message.vue"
;
...
...
@@ -168,12 +169,12 @@
import
PayMessage
from
"./message-item/pay-message.vue"
;
import
NotifyMessage
from
"./message-item/notify-message.vue"
;
import
{
ChatRole
}
from
"@/customer-service/model"
;
import
{
getUserMapping
}
from
"../utils/user-info"
;
import
{
ChatUserInfoService
,
getUserMapping
}
from
"../utils/user-info"
;
import
Xim
from
"@/customer-service/xim"
;
import
{
CustomerServiceEvent
,
MessageEvent
}
from
"../event"
;
import
{
PayMessageBody
}
from
"../xim/models/chat"
;
const
twoMinutes
=
2
*
60
*
1000
;
const
oneDay
=
24
*
60
*
60
*
1000
;
const
messageMapping
=
new
Map
<
dto
.
MessageType
,
string
>
([
[
dto
.
MessageType
.
Image
,
"image-message"
],
...
...
@@ -219,8 +220,8 @@
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_USER_UID
)
private
readonly
chatMyId
!
:
ChatStore
.
STATE_CHAT_CURRENT_USER_UID
;
@
chatStore
.
Getter
(
ChatStore
.
STATE_CURRENT
_CHAT_MEMBERS
)
private
readonly
allChatMembers
!
:
ChatStore
.
STATE_
CURRENT
_CHAT_MEMBERS
;
@
chatStore
.
State
(
ChatStore
.
STATE_ALL_HISTORY
_CHAT_MEMBERS
)
private
readonly
allChatMembers
!
:
ChatStore
.
STATE_
ALL_HISTORY
_CHAT_MEMBERS
;
@
chatStore
.
Getter
(
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
)
private
readonly
chatMembers
!
:
ChatStore
.
GETTER_CURRENT_CHAT_PRESENT_MEMBERS
;
...
...
@@ -228,9 +229,6 @@
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStore
.
Mutation
(
ChatStore
.
MUTATION_WITHDRAW
)
private
readonly
executeWithDraw
!
:
ChatStore
.
MUTATION_WITHDRAW
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_SET_HANDLED
)
private
readonly
setHandled
!
:
ChatStore
.
ACTION_SET_HANDLED
;
...
...
@@ -266,12 +264,22 @@
private
org
=
""
;
private
manualAllRead
=
false
;
private
manualReaded
=
0
;
private
refetchUsername
=
""
;
private
refetchUserIcon
=
""
;
private
readerListOffset
=
false
;
private
defaultMessageHandledStatus
=
dto
.
MessageHandled
.
Default
;
private
isWithdraw
=
true
;
private
get
isSystemMessage
()
{
return
(
this
.
messageBody
&&
this
.
messageBody
.
eid
&&
+
this
.
messageBody
.
eid
===
0
);
}
private
get
isPayMessage
()
{
return
dto
.
MessageTypeController
.
isPayMessage
(
this
.
data
.
type
);
}
...
...
@@ -281,14 +289,17 @@
if
(
this
.
isPayMessage
)
{
return
true
;
}
if
(
this
.
needReadTip
)
{
return
new
Date
().
valueOf
()
-
this
.
data
.
ts
*
1000
<
twoMinutes
;
if
(
this
.
needReadTip
&&
this
.
isMyMessage
)
{
return
new
Date
().
valueOf
()
-
this
.
data
.
ts
*
1000
<
oneDay
;
}
}
return
false
;
}
private
get
needReadTip
()
{
if
(
this
.
isSystemMessage
)
{
return
false
;
}
return
(
this
.
data
.
type
!==
dto
.
MessageType
.
Pay
&&
this
.
data
.
type
!==
dto
.
MessageType
.
Refund
&&
...
...
@@ -344,6 +355,15 @@
return
true
;
}
// 系统推送的消息或老用户(一般是客服,eid为负数),默认为客服发送
if
(
this
.
messageBody
&&
this
.
messageBody
.
eid
&&
+
this
.
messageBody
.
eid
<=
0
)
{
return
true
;
}
if
(
this
.
backend
&&
this
.
messageBody
&&
...
...
@@ -370,24 +390,37 @@
}
private
get
userName
()
{
if
(
this
.
refetchUsername
)
{
return
this
.
refetchUsername
;
}
if
(
this
.
chatMembers
)
{
const
t
=
this
.
chatMembers
.
find
((
i
)
=>
i
.
eid
===
this
.
data
.
eid
);
if
(
t
)
{
return
this
.
getFilterUsername
(
t
.
alias_name
||
t
.
name
);
const
name
=
this
.
getFilterUsername
(
t
.
alias_name
as
string
,
t
.
name
);
if
(
name
)
{
return
name
;
}
}
}
return
""
;
this
.
refetchUsername4Message
();
return
this
.
refetchUsername
;
}
private
getFilterUsername
(
name
:
string
)
{
private
getFilterUsername
(
name1
:
string
,
name2
:
string
)
{
const
backend
=
Xim
.
isBackend
();
if
(
this
.
currentChat
&&
this
.
currentChat
.
catalog
===
"福利宝"
&&
this
.
chatRole
===
"customer-service"
)
{
return
`采购顾问
${
name
}
`
;
return
backend
&&
name1
?
`采购顾问
${
name1
}
(
${
name2
}
)`
:
`采购顾问
${
name1
||
name2
}
`
;
}
return
name
;
return
backend
&&
name1
?
`
${
name1
}
(
${
name2
}
)`
:
name1
||
name2
;
}
private
get
avatar
()
{
...
...
@@ -400,7 +433,7 @@
}
}
return
""
;
return
this
.
refetchUserIcon
;
}
private
get
defaultAvatar
()
{
...
...
@@ -525,14 +558,7 @@
},
});
}
ximInstance
.
withdraw
(
this
.
chatId
,
this
.
data
.
id
).
finally
(()
=>
{
dbController
.
removeMessage
(
this
.
chatId
,
[
this
.
data
.
id
])
.
finally
(()
=>
{
this
.
executeWithDraw
([
this
.
data
.
id
]);
this
.
$emit
(
"withdraw"
,
this
.
data
.
id
);
});
});
ximInstance
.
withdraw
(
this
.
chatId
,
this
.
data
.
id
);
}
private
hoverWithdraw
()
{
...
...
@@ -543,12 +569,14 @@
return
false
;
}
return
(
this
.
isWithdraw
=
new
Date
().
valueOf
()
-
this
.
data
.
ts
*
1000
<
twoMinutes
);
new
Date
().
valueOf
()
-
this
.
data
.
ts
*
1000
<
oneDay
);
}
private
openReaderList
(
e
:
MouseEvent
)
{
this
.
readerListOffset
=
e
.
x
<
450
;
this
.
readListVisibility
=
true
;
if
(
this
.
isChatMember
)
{
this
.
readerListOffset
=
e
.
x
<
450
;
this
.
readListVisibility
=
true
;
}
}
private
executeHandled
()
{
...
...
@@ -576,6 +604,24 @@
private
openMessage
(
o
:
any
)
{
CustomerServiceEvent
.
emit
(
this
,
o
);
}
private
refetchUsername4Message
()
{
if
(
this
.
data
&&
this
.
data
.
eid
)
{
ChatUserInfoService
.
getUserInfo
(
this
.
data
.
eid
).
then
((
r
)
=>
{
if
(
r
)
{
if
(
Xim
.
isBackend
()
&&
r
.
alias_name
)
{
this
.
refetchUsername
=
`
${
r
.
alias_name
}
(
${
r
.
name
||
r
.
phone
}
)`
;
}
else
{
this
.
refetchUsername
=
r
.
alias_name
||
r
.
name
||
r
.
phone
;
}
r
.
icon
&&
(
this
.
refetchUserIcon
=
r
.
icon
);
}
});
}
}
}
</
script
>
...
...
@@ -694,7 +740,12 @@
}
.all
{
color
:
#4389f8
;
color
:
#ccc
;
opacity
:
0.7
;
}
.not-all
{
color
:
#077aec
;
}
.match-keyword
{
...
...
database/dev-tools.ts
View file @
2634302f
...
...
@@ -79,6 +79,61 @@ class DevAppTools {
});
});
}
public
getDataByKey
(
key
:
string
)
{
return
new
Promise
<
any
>
((
resolve
)
=>
{
this
.
onReady
().
finally
(()
=>
{
if
(
!
this
.
db
)
{
return
resolve
(
false
);
}
setTimeout
(()
=>
{
const
store
=
this
.
buildStore
(
this
.
table
);
const
r
=
store
.
get
(
key
);
r
.
onsuccess
=
((
o
)
=>
{
const
result
=
(
o
.
target
as
any
).
result
;
if
(
result
)
{
resolve
(
result
.
content
)
}
else
{
resolve
(
result
)
}
});
r
.
onerror
=
()
=>
resolve
(
false
);
},
300
);
});
});
}
public
addData
(
data
:
any
,
key
:
string
)
{
return
new
Promise
<
boolean
>
((
resolve
)
=>
{
this
.
onReady
().
finally
(()
=>
{
if
(
!
this
.
db
)
{
return
resolve
(
false
);
}
setTimeout
(()
=>
{
const
store
=
this
.
buildStore
(
this
.
table
);
const
r
=
store
.
add
({
value
:
key
,
content
:
data
});
r
.
onsuccess
=
()
=>
resolve
(
true
);
r
.
onerror
=
()
=>
resolve
(
false
);
},
300
);
});
});
}
public
deleteData
(
key
:
string
)
{
return
new
Promise
<
boolean
>
((
resolve
)
=>
{
this
.
onReady
().
finally
(()
=>
{
if
(
!
this
.
db
)
{
return
resolve
(
false
);
}
setTimeout
(()
=>
{
const
store
=
this
.
buildStore
(
this
.
table
);
const
r
=
store
.
delete
(
key
);
r
.
onsuccess
=
()
=>
resolve
(
true
);
r
.
onerror
=
()
=>
resolve
(
false
);
},
300
);
});
});
}
}
export
const
devAppTools
=
new
DevAppTools
();
database/index.ts
View file @
2634302f
...
...
@@ -152,8 +152,12 @@ class ChatCacheDatabaseController {
public
saveChatList
(
items
:
Chat
[])
{
if
(
this
.
db
)
{
const
store
=
this
.
buildStore
(
this
.
chatListKey
);
for
(
const
item
of
items
)
{
store
.
add
(
item
,
item
.
id
);
if
(
items
&&
items
.
length
)
{
for
(
const
item
of
items
)
{
store
.
add
(
item
,
item
.
id
);
}
}
else
{
store
.
clear
();
}
}
}
...
...
@@ -375,7 +379,7 @@ class ChatCacheDatabaseController {
public
appendMessages
(
chat
:
number
,
items
:
Message
[])
{
return
new
Promise
<
void
>
(
resolve
=>
{
if
(
!
this
.
db
||
!
items
.
length
)
{
if
(
!
this
.
db
||
!
items
||
!
items
.
length
)
{
return
resolve
();
}
const
store
=
this
.
buildChatMessageStore
(
chat
);
...
...
@@ -459,49 +463,6 @@ class ChatCacheDatabaseController {
});
});
}
public
syncChats
(
chats
:
number
[])
{
return
new
Promise
<
void
>
(
resolve
=>
{
if
(
this
.
db
)
{
this
.
getChatList
().
then
(
r
=>
{
const
set
=
new
Set
<
number
>
(
chats
);
let
finished
=
0
;
let
removing
=
[];
for
(
const
item
of
r
)
{
if
(
!
set
.
has
(
item
.
id
))
{
removing
.
push
(
item
.
id
);
}
}
for
(
const
item
of
removing
)
{
this
.
removeChatAndMessages
(
item
).
finally
(()
=>
{
finished
++
;
if
(
finished
===
removing
.
length
)
{
resolve
();
}
});
}
!
removing
.
length
&&
resolve
();
});
}
else
{
resolve
();
}
});
}
private
removeChatAndMessages
(
chat
:
number
)
{
this
.
setupChatMessageDatabase
(
chat
).
finally
(()
=>
{
const
store
=
this
.
buildChatMessageStore
(
chat
);
const
r
=
store
.
clear
();
r
.
onsuccess
=
()
=>
{
indexedDB
.
deleteDatabase
(
this
.
buildChatMessageKey
(
chat
));
};
});
return
this
.
removeChatFromList
(
chat
);
}
}
export
const
dbController
=
new
ChatCacheDatabaseController
();
hybrid-input/index.vue
View file @
2634302f
...
...
@@ -125,6 +125,10 @@
);
}
export
function
isFileElement
(
node
:
ChildNode
)
{
return
(
node
as
Element
).
classList
.
contains
(
FILE_INFO_CLASS
);
}
const
limitedFileExtension
=
[
"ppt"
,
"pptx"
,
...
...
@@ -149,9 +153,6 @@
@
chatStore
.
State
(
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
)
private
readonly
chatId
!
:
ChatStore
.
STATE_CHAT_CURRENT_CHAT_ID
;
@
chatStore
.
Action
(
ChatStore
.
ACTION_GET_MY_CHAT_LIST
)
protected
readonly
getMyChatList
!
:
ChatStore
.
ACTION_GET_MY_CHAT_LIST
;
@
Ref
(
"input"
)
private
readonly
messageInputBox
!
:
HTMLDivElement
;
...
...
@@ -339,16 +340,18 @@
if
(
e
.
shiftKey
||
e
.
ctrlKey
||
e
.
altKey
)
{
return
;
}
const
data
=
this
.
getNodeListFromInputBox
();
this
.
$emit
(
"send"
,
data
);
this
.
clearInput
();
if
(
this
.
chatId
)
{
chatCache
[
this
.
chatId
]
=
[];
}
// 避免按一下enter键多次触发发送
if
(
this
.
reloadTimer
)
{
clearTimeout
(
this
.
reloadTimer
);
}
this
.
reloadTimer
=
setTimeout
(()
=>
this
.
getMyChatList
(),
120
);
this
.
reloadTimer
=
setTimeout
(()
=>
{
const
data
=
this
.
getNodeListFromInputBox
();
this
.
$emit
(
"send"
,
data
);
this
.
clearInput
();
if
(
this
.
chatId
)
{
chatCache
[
this
.
chatId
]
=
[];
}
},
120
);
}
/**
...
...
@@ -366,30 +369,17 @@
*/
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
);
}
if
(
isImageOrFile
(
item
))
{
sendingNodes
.
push
(
item
);
continue
;
}
if
(
item
.
textContent
)
{
const
text
=
item
.
textContent
;
this
.
checkTextLength
(
text
);
const
node
=
document
.
createTextNode
(
text
);
sendingNodes
.
push
(
node
);
}
}
if
(
text
)
{
this
.
checkTextLength
(
text
);
const
node
=
document
.
createTextNode
(
text
);
sendingNodes
.
push
(
node
);
}
return
sendingNodes
;
...
...
model/im.ts
View file @
2634302f
...
...
@@ -174,15 +174,15 @@ export const imItems = [
// 亲亲小保
{
type
:
IMDomainType
.
社保客服
,
title
:
"
社保客服
"
,
title
:
"
在线咨询
"
,
},
{
type
:
IMDomainType
.
pc
网站咨询
,
title
:
"
pc网站
咨询"
,
title
:
"
在线
咨询"
,
},
{
type
:
IMDomainType
.
手机官网咨询
,
title
:
"
手机官网
咨询"
,
title
:
"
在线
咨询"
,
},
{
type
:
IMDomainType
.
问答动态提醒
,
...
...
model/index.ts
View file @
2634302f
...
...
@@ -82,6 +82,8 @@ export interface ChatOption {
message
?:
ChatMessageController
;
avatar
?:
string
;
disabledDbIndex
?:
boolean
;
}
export
interface
ChatMessageController
{
...
...
service/monitor.ts
View file @
2634302f
import
Axios
,
{
AxiosResponse
}
from
"axios"
;
import
Axios
,
{
AxiosResponse
,
AxiosAdapter
}
from
"axios"
;
import
{
UniplatSdk
}
from
"uniplat-sdk"
;
import
{
ImEnvironment
}
from
"../model"
;
export
const
enum
Product
{
Default
=
'default'
,
QqxbWeixin
=
'qqxb-weixin'
,
QqxbApp
=
'qqxb-app'
,
Fulibao
=
'fulibao'
,
HrManager
=
'hr-manager'
,
Hrs100
=
'hrs100'
,
HrsApp
=
'hrs-app'
,
BiJie
=
'bi-jie'
,
Cashier
=
'cashier'
,
Default
=
"default"
,
QqxbWeixin
=
"qqxb-weixin"
,
QqxbApp
=
"qqxb-app"
,
Fulibao
=
"fulibao"
,
HrManager
=
"hr-manager"
,
Hrs100
=
"hrs100"
,
HrsApp
=
"hrs-app"
,
BiJie
=
"bi-jie"
,
Cashier
=
"cashier"
,
Uniplat
=
"uniplat"
,
DeShengJiuYeBao
=
'de-sheng-jiu-ye-bao'
DeShengJiuYeBao
=
"de-sheng-jiu-ye-bao"
,
}
const
enum
ProductTable
{
...
...
@@ -26,6 +26,7 @@ export interface SdkMonitorOption {
userAgent
?:
boolean
;
envir
:
ImEnvironment
;
product
:
Product
;
call
?:
(
r
:
any
)
=>
void
;
}
class
WebMonitor
{
...
...
@@ -33,6 +34,7 @@ class WebMonitor {
private
envir
=
ImEnvironment
.
Dev
;
private
product
=
Product
.
Default
;
private
readonly
url
=
"https://pre-hrs-monitor.hrs100.com"
;
private
adapter
:
AxiosAdapter
|
undefined
;
public
updateKey
(
key
:
string
)
{
this
.
key
=
key
;
...
...
@@ -42,6 +44,7 @@ class WebMonitor {
private
buildHeaders
()
{
return
{
headers
:
{
authorization
:
"cdd0a34e-f537-4e5b-808e-2ba06af21845"
},
adapter
:
this
.
adapter
,
};
}
...
...
@@ -85,35 +88,59 @@ class WebMonitor {
);
}
public
useSdk
(
sdk
:
UniplatSdk
,
options
:
SdkMonitorOption
)
{
public
useSdk
(
sdk
:
UniplatSdk
,
options
:
SdkMonitorOption
,
adapter
?:
AxiosAdapter
)
{
this
.
envir
=
options
.
envir
;
this
.
product
=
options
.
product
;
sdk
.
events
.
addUniversalErrorResponseCallback
((
r
:
AxiosResponse
<
any
>
)
=>
{
if
(
this
.
enable
())
{
const
msg
:
string
[]
=
[];
msg
.
push
(
`URL:
${
decodeURIComponent
(
r
.
config
.
url
as
string
)}
`
);
msg
.
push
(
`Token:
${
sdk
.
global
.
jwtToken
}
`
);
const
header
=
r
.
config
.
headers
;
if
(
header
)
{
msg
.
push
(
`CurrentOrg:
${
header
.
CurrentOrg
}
`
);
msg
.
push
(
`Scenes:
${
header
.
Scenes
}
`
);
}
this
.
adapter
=
adapter
;
sdk
.
events
.
addUniversalErrorResponseCallback
(
(
r
:
AxiosResponse
<
any
>
)
=>
{
options
.
call
&&
options
.
call
(
r
);
if
(
this
.
enable
())
{
const
msg
:
string
[]
=
[];
msg
.
push
(
`URL:
${
decodeURIComponent
(
r
.
config
.
url
as
string
)}
`
);
msg
.
push
(
`Token:
${
sdk
.
global
.
jwtToken
}
`
);
const
header
=
r
.
config
.
headers
;
if
(
header
)
{
msg
.
push
(
`CurrentOrg:
${
header
.
CurrentOrg
}
`
);
msg
.
push
(
`Scenes:
${
header
.
Scenes
}
`
);
}
options
&&
options
.
userAgent
&&
msg
.
push
(
`UserAgent:
${
window
.
navigator
.
userAgent
}
`
);
r
.
config
&&
r
.
config
.
data
&&
msg
.
push
(
`Payload:
${
JSON
.
stringify
(
r
.
config
.
data
)}
`
);
options
&&
options
.
userAgent
&&
msg
.
push
(
`UserAgent:
${
window
.
navigator
.
userAgent
}
`
);
r
.
config
&&
r
.
config
.
params
&&
msg
.
push
(
`Params:
${
JSON
.
stringify
(
r
.
config
.
params
)}
`
);
if
(
r
.
config
&&
r
.
config
.
data
)
{
const
form
=
r
.
config
.
data
as
FormData
;
if
(
form
.
getAll
)
{
const
p
=
form
.
getAll
(
"parameters"
);
for
(
const
item
of
p
)
{
msg
.
push
(
`Payload:
${
item
}
`
);
}
}
else
{
msg
.
push
(
`Payload:
${
JSON
.
stringify
(
r
.
config
.
data
)}
`
);
}
}
msg
.
push
(
`Exception:
${((
r
.
data
.
error
as
string
)
||
""
).
substring
(
0
,
500
)}
`
);
msg
.
push
(
`Exception:
${(
(
r
.
data
.
error
as
string
)
||
""
).
substring
(
0
,
500
)}
`
);
r
&&
r
.
config
&&
this
.
error
(
msg
.
join
(
"\n"
));
r
&&
r
.
config
&&
this
.
error
(
msg
.
join
(
"\n"
));
}
}
}
);
);
}
}
...
...
service/order.ts
View file @
2634302f
...
...
@@ -138,16 +138,19 @@ class OrderService {
r
.
pageData
.
rows
,
orderPayItemPredict
);
items
=
items
.
filter
(
(
i
)
=>
i
.
status
!==
PayStatus
.
Deleted
&&
i
.
status
!==
PayStatus
.
Cancel
);
if
(
!
withActions
)
{
items
=
items
.
filter
(
(
i
)
=>
i
.
status
!==
PayStatus
.
Deleted
&&
i
.
status
!==
PayStatus
.
Cancel
);
}
if
(
withActions
)
{
for
(
let
i
=
0
;
i
<
r
.
pageData
.
rows
.
length
;
i
++
)
{
r
.
pageData
&&
r
.
pageData
.
rows
&&
r
.
pageData
.
rows
[
i
]
&&
items
[
i
]
&&
(
items
[
i
].
actions
=
r
.
pageData
.
rows
[
i
].
actions
);
}
}
...
...
store/index.ts
View file @
2634302f
This diff is collapsed.
Click to expand it.
store/model.ts
View file @
2634302f
...
...
@@ -73,6 +73,11 @@ export namespace ChatStore {
|
readonly
(
dto
.
ChatMember
&
dto
.
ChatMemberExtraInfo
)[]
|
null
;
export
const
STATE_ALL_HISTORY_CHAT_MEMBERS
=
"当前会话历史所有参与者"
;
export
type
STATE_ALL_HISTORY_CHAT_MEMBERS
=
|
readonly
(
dto
.
ChatMember
&
dto
.
ChatMemberExtraInfo
)[]
|
null
;
export
const
STATE_CURRENT_CHAT_TITLE
=
"会话标题"
;
export
type
STATE_CURRENT_CHAT_TITLE
=
string
;
...
...
@@ -247,12 +252,6 @@ export namespace ChatStore {
keyword
?:
string
)
=>
Promise
<
ChatType
[]
>
;
export
const
ACTION_SYNC_MY_CHAT_LIST
=
"同步我的会话列表"
;
export
type
ACTION_SYNC_MY_CHAT_LIST
=
()
=>
void
;
export
const
ACTION_FORCE_RELOAD_CHAT_LIST
=
"重新获取我的会话列表"
;
export
type
ACTION_FORCE_RELOAD_CHAT_LIST
=
()
=>
Promise
<
ChatType
[]
>
;
export
const
ACTION_REBUILD_UNREAD_MESSAGE_COUNT
=
"重新计算未读消息数"
;
export
type
ACTION_REBUILD_UNREAD_MESSAGE_COUNT
=
()
=>
void
;
...
...
utils/chat-info.ts
View file @
2634302f
import
Chat
from
"../xim"
;
import
{
orderService
}
from
'../service/order'
;
import
{
orderService
}
from
"../service/order"
;
export
type
ChatInfo
=
{
[
eid
:
string
]:
any
;
...
...
@@ -10,7 +10,8 @@ const chatInfo: ChatInfo = {};
export
const
getChatModel
=
()
=>
chatInfo
;
const
loadingKeys
=
new
Set
<
string
>
();
let
waitingAction
:
{
key
:
string
;
resolve
:
(
d
:
ChatModelInfoData
)
=>
void
}[]
=
[];
let
waitingAction
:
{
key
:
string
;
resolve
:
(
d
:
ChatModelInfoData
)
=>
void
}[]
=
[];
export
interface
ChatModelInfoData
{
uniplatId
:
string
|
number
;
...
...
@@ -25,7 +26,10 @@ function buildCache() {
if
(
!
model2DetailNameMapping
.
size
)
{
// 用户端默认不使用chat内置的detailName(这个专属于服务端),所以这里加一层内置转换
if
(
!
Chat
.
isBackend
())
{
model2DetailNameMapping
.
set
(
orderService
.
generalOrder
,
orderService
.
generalOrderDefaultDetailName
);
model2DetailNameMapping
.
set
(
orderService
.
generalOrder
,
orderService
.
generalOrderDefaultDetailName
);
}
}
}
...
...
@@ -57,12 +61,6 @@ export async function getChatModelInfo(
data
:
d
,
}
as
ChatModelInfoData
);
}
return
Promise
.
resolve
({
uniplatId
:
0
,
chat_id
:
0
,
uniplat_version
:
0
,
data
:
d
,
});
}
loadingKeys
.
add
(
key
);
...
...
@@ -71,26 +69,26 @@ export async function getChatModelInfo(
.
detail
(
id
+
""
,
detail
)
.
query
();
const
data
=
info
;
chatInfo
[
key
]
=
data
;
info
&&
info
.
row
.
UniplatChatId
&&
(
chatInfo
[
key
]
=
data
)
;
loadingKeys
.
delete
(
key
);
const
o
=
(
info
.
row
&&
info
.
row
.
UniplatChatId
?
{
uniplatId
:
info
.
row
.
UniplatChatId
.
value
,
chat_id
:
+
(
info
.
row
.
UniplatImChatId
.
value
as
string
),
uniplat_version
:
0
,
data
,
}
uniplatId
:
info
.
row
.
UniplatChatId
.
value
,
chat_id
:
+
(
info
.
row
.
UniplatImChatId
.
value
as
string
),
uniplat_version
:
0
,
data
,
}
:
{
uniplatId
:
0
,
chat_id
:
0
,
uniplat_version
:
0
,
data
,
}
uniplatId
:
0
,
chat_id
:
0
,
uniplat_version
:
0
,
data
,
}
)
as
ChatModelInfoData
;
le
t
removing
=
[];
cons
t
removing
=
[];
for
(
const
item
of
waitingAction
)
{
if
(
item
.
key
===
key
)
{
item
.
resolve
(
o
);
...
...
utils/index.ts
View file @
2634302f
...
...
@@ -56,6 +56,17 @@ export function uuid() {
return
s
.
join
(
""
)
}
export
function
copyTextToClipboard
(
text
:
string
)
{
const
input
=
document
.
createElement
(
"input"
);
input
.
setAttribute
(
"readonly"
,
"readonly"
);
input
.
setAttribute
(
"value"
,
text
);
document
.
body
.
appendChild
(
input
);
input
.
select
();
const
ret
=
document
.
execCommand
(
"copy"
);
document
.
body
.
removeChild
(
input
);
return
ret
;
}
const
URL_REGEX
=
/
((?:(?:
https
?
|ftp|file
)
:
\/\/
|www
\.
|ftp
\.)(?:\([
-A-Z0-9+&@#
\/
%=~_|$?!:,.
]
*
\)
|
[
-A-Z0-9+&@#
\/
%=~_|$?!:,.
])
*
(?:\([
-A-Z0-9+&@#
\/
%=~_|$?!:,.
]
*
\)
|
[
A-Z0-9+&@#
\/
%=~_|$
]))
/gim
...
...
utils/user-info.ts
View file @
2634302f
import
{
UniplatSdk
}
from
"uniplat-sdk"
;
import
Chat
from
"../xim"
;
export
type
UserMapping
=
{
[
eid
:
string
]:
{
nam
e
:
string
;
phone
:
string
;
icon
:
string
;
alias_name
:
string
;
};
};
export
interface
ChatUserSummaryInfo
{
name
:
string
;
phon
e
:
string
;
icon
:
string
;
alias_name
:
string
;
}
export
type
UserMapping
=
{
[
eid
:
string
]:
ChatUserSummaryInfo
};
const
userMapping
:
UserMapping
=
{};
...
...
@@ -25,6 +25,12 @@ interface UserInfo {
export
const
getUserMapping
=
()
=>
userMapping
;
const
loadingKeys
=
new
Set
<
string
>
();
let
waitingAction
:
{
key
:
string
;
resolve
:
(
d
:
ChatUserSummaryInfo
)
=>
void
;
}[]
=
[];
export
class
ChatUserInfoService
{
public
static
async
getUserInfo
(
eid
:
string
,
sdk
?:
UniplatSdk
)
{
if
(
userMapping
[
eid
])
{
...
...
@@ -33,6 +39,15 @@ export class ChatUserInfoService {
if
(
!+
eid
||
+
eid
<
0
)
{
return
{
name
:
""
,
phone
:
""
,
icon
:
""
,
alias_name
:
""
};
}
if
(
loadingKeys
.
has
(
eid
))
{
return
new
Promise
<
ChatUserSummaryInfo
>
((
resolve
)
=>
waitingAction
.
push
({
key
:
eid
,
resolve
})
);
}
loadingKeys
.
add
(
eid
);
const
info
=
await
(
sdk
||
Chat
.
getSdk
())
.
domainService
(
"passport"
,
...
...
@@ -47,6 +62,21 @@ export class ChatUserInfoService {
alias_name
:
info
.
alias_name
,
};
userMapping
[
eid
]
=
data
;
const
removing
=
[];
for
(
const
item
of
waitingAction
)
{
if
(
item
.
key
===
eid
)
{
item
.
resolve
(
data
);
removing
.
push
(
item
.
key
);
}
}
for
(
const
item
of
removing
)
{
waitingAction
=
waitingAction
.
filter
((
i
)
=>
i
.
key
!==
item
);
}
loadingKeys
.
delete
(
eid
);
return
data
;
}
...
...
xim/index.ts
View file @
2634302f
...
...
@@ -58,10 +58,12 @@ class Chat {
option
.
message
&&
(
this
.
messageController
=
option
.
message
);
option
.
avatar
!==
undefined
&&
(
this
.
defaultAvatar
=
option
.
avatar
);
await
this
.
setupIndexDb
(
option
.
orgId
()).
catch
(
err
=>
{
// 必须catch error不然小程序不会向后运行
console
.
error
(
"setupIndexDb Error"
)
});
if
(
!
option
.
disabledDbIndex
){
await
this
.
setupIndexDb
(
option
.
orgId
()).
catch
(
err
=>
{
// 必须catch error不然小程序不会向后运行
console
.
error
(
"setupIndexDb Error"
)
});
}
this
.
token
=
async
()
=>
option
.
sdk
().
global
.
jwtToken
;
tokenManager
.
save
(
this
.
token
);
...
...
@@ -154,7 +156,7 @@ class Chat {
.
finally
(()
=>
{
this
.
registerXimEvent
();
if
(
xim
.
isConnected
())
{
resolve
(
);
setTimeout
(
resolve
,
200
);
}
else
{
reject
(
new
Error
(
`xim is not connected`
));
}
...
...
xim/models/chat.ts
View file @
2634302f
...
...
@@ -55,6 +55,7 @@ export interface Chat {
biz_type_code
:
string
;
business_data
?:
string
;
detail_name
?:
string
;
keyword
?:
string
;
}
export
interface
Message
{
...
...
@@ -230,6 +231,8 @@ export interface PositionMessage {
company_name
:
string
;
business_scope
:
string
;
post_id
:
number
;
max_salary
:
number
;
min_salary
:
number
;
}
export
interface
CsUser
{
...
...
xim/xim.ts
View file @
2634302f
...
...
@@ -165,6 +165,7 @@ export class Xim {
lid
=
0
,
rid
=
0
,
limit
=
DefaultMsgPageSize
,
// = 0 正序(最新的消息在最下面),=1 倒序(最新的消息在最上面)
desc
:
boolean
,
p
?:
{
isMember
:
boolean
;
model
:
string
;
obj
:
string
}
):
Promise
<
Message
[]
>
{
...
...
@@ -195,8 +196,12 @@ export class Xim {
.
getSdk
()
.
getAxios
()
.
get
<
any
,
Message
[]
>
(
`/general/xim/model/
${
p
.
model
}
/
${
p
.
obj
}
/msgs?lid=
${
lid
}
&rid=
${
rid
}
&limit=
${
limit
}
&desc=
${
desc
?
0
:
1
}
`
)
`/general/xim/model/
${
p
.
model
}
/
${
p
.
obj
}
/msgs?lid=
${
lid
}
&rid=
${
rid
}
&limit=
${
limit
}
&desc=
${
desc
?
1
:
0
}
`
);
}
private
setMessagesRead
(
chatId
:
number
,
msg
:
Message
[])
{
...
...
@@ -431,9 +436,12 @@ export class Xim {
vue
.
$once
(
"hook:beforeDestroy"
,
()
=>
this
.
off
(
"msg"
,
action
));
}
public
withDrawMsgHandle
(
e
:
Message
)
{
const
ids
=
e
.
ref_id
?
[
e
.
ref_id
]
:
e
.
msg
.
startsWith
(
"["
)
?
JSON
.
parse
(
e
.
msg
)
:
[
+
e
.
msg
];
return
ids
;
public
withDrawMsgHandle
(
e
:
Message
):
number
[]
{
return
e
.
ref_id
?
[
e
.
ref_id
]
:
e
.
msg
.
startsWith
(
"["
)
?
JSON
.
parse
(
e
.
msg
)
:
[
+
e
.
msg
];
}
}
...
...
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