Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
T
tokcos-socket-web-ts-user
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
小豪
tokcos-socket-web-ts-user
Commits
749e555b
提交
749e555b
authored
4月 11, 2024
作者:
Hao
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
add
父级
e861a5d9
显示空白字符变更
内嵌
并排
正在显示
8 个修改的文件
包含
245 行增加
和
1245 行删除
+245
-1245
src/App.vue
+3
-11
src/axios/index.ts
+1
-1
src/axios/model/user.ts
+3
-3
src/components/AutomaticPrompt.vue
+135
-201
src/components/IndexComponent.vue
+7
-5
src/utils/websocket.ts
+1
-3
src/views/HomeView copy.vue
+0
-936
src/views/HomeView.vue
+95
-85
没有找到文件。
src/App.vue
浏览文件 @
749e555b
<
template
>
<div>
<header>
<div
class=
"operation"
>
<el-button
@
click=
"changeLang('zhcn')"
class=
"mr10"
>
中文
</el-button>
<el-button
@
click=
"changeLang('en')"
class=
"mr10"
>
英文
</el-button>
<el-button
@
click=
"changeLang('ko')"
class=
"mr10"
>
韩文
</el-button>
<el-button
@
click=
"changeLang('ja')"
class=
"mr10"
>
日文
</el-button>
</div>
</header>
<router-view
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
const
changeLang
=
(
value
:
any
)
=>
{
const
changeLang
=
(
value
:
any
)
=>
{
localStorage
.
setItem
(
'lang'
,
value
)
location
.
reload
();
}
location
.
reload
()
}
</
script
>
<
style
lang=
"scss"
>
*
{
...
...
src/axios/index.ts
浏览文件 @
749e555b
// axios.js
import
axios
from
"axios"
;
const
instance
=
axios
.
create
({
baseURL
:
"http://192.168.31.
228
:8080"
,
// 设置基础 URL
baseURL
:
"http://192.168.31.
123
:8080"
,
// 设置基础 URL
// baseURL:'/api',
timeout
:
1000
,
// 设置请求超时时间
});
...
...
src/axios/model/user.ts
浏览文件 @
749e555b
...
...
@@ -27,7 +27,7 @@ const Kjiehuifu = (query: any) => {
//获取用户列表
const
getUserList
=
(
query
:
any
)
=>
{
return
http
({
url
:
"/
getUser
List"
,
url
:
"/
kf/chat/getChat
List"
,
method
:
"get"
,
data
:
query
,
});
...
...
@@ -35,9 +35,9 @@ const getUserList = (query: any) => {
//查看未读消息
const
checkMesssages
=
(
query
:
any
)
=>
{
return
http
({
url
:
"/
checkMesssages
"
,
url
:
"/
kf/chat/getChatHisList
"
,
method
:
"get"
,
data
:
query
,
params
:
query
,
});
};
//上传接口
...
...
src/components/AutomaticPrompt.vue
浏览文件 @
749e555b
<
template
>
<div>
<div
class=
"auto-prompt"
>
<div
class=
"chat-textarea"
>
<div
class=
"chat-bar"
>
<div>
...
...
@@ -31,36 +31,19 @@
快捷回复
</div>
</div>
<!--
<el-autocomplete
class=
"autocomplete"
v-model=
"state"
:fetch-suggestions=
"querySearchAsync"
placeholder=
"请输入问题"
type=
"textarg"
@
select=
"handleSelect"
ref=
"automaticPromptRef"
/>
-->
<!--
<el-input
v-model=
"state"
ref=
"automaticPromptRef"
class=
"el-autocomplete"
placeholder=
"输入信息"
type=
"textarea"
resize=
"none"
@
change=
"handleSelect"
/>
-->
<div
class=
"el-autocomplete"
class=
"el-autocomplete
textarea
"
id=
"el-autocomplete"
ref=
"el-autocomplete"
ref=
"elAutocomplete"
placeholder=
"输入信息"
contenteditable=
"true"
style=
"outline: none;height: 150px;"
@
paste=
"onPaste"
style=
"outline: none; height: 100%;"
@
paste
.
prevent=
"handlePaste"
@
focus=
"removeDefaultContent"
@
input=
"handleSelect"
>
</div>
@
blur=
"addDefaultContent"
>
</div>
</div>
<emotion
v-if=
"showEmotion"
style=
"position: absolute; top: calc(100% - 465px); background: #fff;"
...
...
@@ -78,229 +61,180 @@
</
template
>
<
script
lang=
"ts"
setup
>
import
E
motion
from
'./IndexComponent.vue'
import
e
motion
from
'./IndexComponent.vue'
import
{
ElLoading
}
from
'element-plus'
import
{
ref
,
watch
,
defineEmits
,
defineExpose
}
from
'vue'
import
{
upLoadFilesHander
}
from
'../minxins/UploadMixin'
import
axios
from
'axios'
const
state
=
ref
(
''
)
const
elAutocomplete
=
ref
(
null
)
const
file
=
ref
(
null
)
interface
LinkItem
{
value
:
string
link
:
string
}
// uploadMixin()
const
inputVal
=
ref
(
''
)
const
elAutocomplete
=
ref
<
any
>
(
null
)
const
file
=
ref
<
any
>
(
null
)
const
imgShowWidth
=
ref
<
number
>
(
50
)
const
imgShowHeight
=
ref
<
number
>
(
50
)
const
showEmotion
=
ref
<
boolean
>
(
false
)
const
automaticPromptRef
=
ref
(
null
)
const
links
=
ref
<
LinkItem
[]
>
([])
const
handleEmotion
=
(
i
)
=>
{
state
.
value
+=
i
const
handleEmotion
=
(
i
:
any
)
=>
{
elAutocomplete
.
value
.
innerHTML
+=
i
inputVal
.
value
=
elAutocomplete
.
value
.
innerHTML
showEmotion
.
value
=
false
automaticPromptRef
.
value
.
focus
()
elAutocomplete
.
value
.
focus
()
}
const
upfile
=
()
=>
{
file
.
value
.
click
()
}
//上传的数据
const
fileChange
=
()
=>
{
var
e
=
window
.
event
||
event
var
e
:
any
=
window
.
event
||
event
var
oFile
=
e
.
target
.
files
[
0
]
upLoadFilesHander
(
oFile
).
then
((
ress
)
=>
{
const
{
success
,
result
}
=
ress
console
.
log
(
result
)
const
{
success
,
result
}:
any
=
ress
})
const
loading
=
ElLoading
.
service
({
const
loading
:
any
=
ElLoading
?
.
service
({
lock
:
true
,
text
:
'上传中...'
,
spinner
:
'el-icon-loading'
,
})
loading
.
close
()
}
const
showWord
=
()
=>
{
console
.
log
(
3
)
}
// const loadFromBackend = async (value: string) => {
// try {
// //输入时候请求后端根据输入值得到提示。 后端返回集合,集合里面对象属性为value和link都是string类型
// const response = await axios.get(
// `http://localhost:8080/getAutoMsg/${value}`,
// )
// links.value = response.data
// } catch (error) {
// console.error(error)
// }
// }
const
querySearchAsync
=
(
queryString
:
string
,
cb
:
(
arg
:
any
)
=>
void
)
=>
{
const
results
=
queryString
?
links
.
value
.
filter
(
createFilter
(
queryString
))
:
links
.
value
cb
(
results
)
}
const
createFilter
=
(
queryString
:
string
)
=>
{
return
(
link
:
LinkItem
)
=>
{
return
link
.
value
.
toLowerCase
().
indexOf
(
queryString
.
toLowerCase
())
===
0
}
}
const
handleSelect
=
(
value
:
string
)
=>
{
state
.
value
=
value
.
target
.
innerHTML
const
handleSelect
=
(
value
:
any
)
=>
{
inputVal
.
value
=
value
.
target
.
innerHTML
}
const
emit
=
defineEmits
([
'updateState'
])
watch
(
state
,
async
(
newValue
)
=>
{
if
(
!
newValue
)
document
.
getElementById
(
'el-autocomplete'
).
innerHTML
=
''
watch
(
inputVal
,
async
(
newValue
)
=>
{
if
(
!
newValue
)
elAutocomplete
.
value
.
innerHTML
=
''
emit
(
'updateState'
,
newValue
)
})
const
getPaste
=
(
e
:
any
)
=>
{
console
.
log
(
e
,
'粘贴'
)
// 如果剪贴板没有数据则直接返回
// 用Promise封装便于将来使用
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
!
(
e
.
clipboardData
&&
e
.
clipboardData
.
items
))
{
reject
(
new
Error
(
'Not allow to paste this type!'
))
}
else
{
// 复制的内容在剪贴板里位置不确定,所以通过遍历来保证数据准确
for
(
let
i
=
0
,
len
=
e
.
clipboardData
.
items
.
length
;
i
<
len
;
i
++
)
{
const
item
=
e
.
clipboardData
.
items
[
i
]
// 文本格式内容处理
if
(
item
.
kind
===
'string'
)
{
item
.
getAsString
((
str
:
any
)
=>
{
resolve
(
str
)
})
// 图片格式内容处理
}
else
if
(
item
.
kind
===
'file'
)
{
const
pasteFile
=
item
.
getAsFile
()
// 处理pasteFile
const
imgEvent
=
{
target
:
{
files
:
[
pasteFile
],
},
}
chooseImg
(
imgEvent
,
(
url
:
any
)
=>
{
resolve
(
url
)
})
}
else
{
reject
(
new
Error
(
'Not allow to paste this type!'
))
}
}
}
})
// 去文本粘贴
const
handlePaste
=
async
(
event
:
any
)
=>
{
event
.
preventDefault
()
const
pasteResult
=
handlePastePlainText
(
event
.
clipboardData
)
if
(
pasteResult
)
return
await
handlePasteImageFile
(
event
.
clipboardData
)
}
const
onPaste
=
(
event
:
any
)
=>
{
// navigator.permissions.query({name: 'clipboard-read'}).then(function(result) {
// if (result.state === 'granted') {
// // 用户已授权访问剪贴板
// } else if (result.state === 'prompt') {
// // 用户尚未授权访问剪贴板,需要提示用户授权
// result.onchange = function() {
// if (result.state === 'granted') {
// // 用户已授权访问剪贴板
// consoel
// } else if (result.state === 'denied') {
// // 用户拒绝了访问剪贴板的请求
// }
// };
// } else if (result.state === 'denied') {
// // 用户拒绝了访问剪贴板的请求
// }
// });
const
items
=
(
event
.
clipboardData
||
window
.
clipboardData
).
items
console
.
log
(
items
)
for
(
let
i
=
0
;
i
<
items
.
length
;
i
++
)
{
if
(
items
[
i
].
type
.
indexOf
(
'image'
)
!==
-
1
)
{
const
file
=
items
[
i
].
getAsFile
()
const
reader
=
new
FileReader
()
reader
.
onload
=
function
(
e
)
{
const
img
=
document
.
createElement
(
'img'
)
img
.
src
=
e
.
target
.
result
img
.
style
.
width
=
'200px'
// 设置图片宽度
img
.
style
.
height
=
'300px'
// 设置图片高度
event
.
target
.
appendChild
(
img
)
// 去格式粘贴 文本
const
handlePastePlainText
=
(
clipboardData
:
any
)
=>
{
const
text
=
clipboardData
.
getData
(
'text/plain'
)
if
(
text
)
{
const
textNode
=
document
.
createTextNode
(
text
)
cursorInsert
(
textNode
)
return
true
}
reader
.
readAsDataURL
(
file
)
return
false
}
//改变光标位置
const
cursorInsert
=
(
node
:
any
)
=>
{
// 获取光标范围
const
selObj
:
any
=
window
.
getSelection
()
const
range
=
selObj
.
getRangeAt
(
0
)
// 光标处插入节点
range
.
insertNode
(
node
)
// 取消insert node 后的选中状态,将光标恢复到 insert node 后面
range
.
collapse
(
false
)
inputVal
.
value
=
elAutocomplete
.
value
.
innerHTML
}
//粘贴图片
const
handlePasteImageFile
=
async
(
clipboardData
:
any
)
=>
{
const
img
=
getPasteImageFile
(
clipboardData
.
files
)
if
(
!
img
)
{
return
}
const
uploadRes
=
await
fileToBase64
(
img
)
if
(
!
uploadRes
)
{
return
}
// e.stopPropagation()
// e.preventDefault()
// var text = '',
// event = e.originalEvent || e
// if (event.clipboardData && event.clipboardData.getData) {
// text = event.clipboardData.getData('text/plain')
// } else if (window.clipboardData && window.clipboardData.getData) {
// text = window.clipboardData.getData('Text')
// }
// if (document.queryCommandSupported('insertText')) {
// document.execCommand('insertText', false, text)
// } else {
// document.execCommand('paste', false, text)
// }
// getPaste(e).then((res: any) => {
// console.log(res, 'res')
// })
const
oImage
=
await
getImageObject
(
uploadRes
,
imgShowWidth
.
value
,
imgShowHeight
.
value
,
)
cursorInsert
(
oImage
)
inputVal
.
value
=
elAutocomplete
.
value
.
innerHTML
}
const
toPreviewer
=
(
dataUrl
,
cb
)
=>
{
cb
&&
cb
(
dataUrl
)
}
// 获取一个 image object
const
getImageObject
=
(
uploadRes
:
any
,
showWidth
:
any
,
showHeight
:
any
)
=>
{
const
oImage
=
new
Image
(
showWidth
,
showHeight
)
const
compress
=
(
img
,
fileType
,
maxWidth
)
=>
{
let
canvas
=
document
.
createElement
(
'canvas'
)
let
ctx
=
canvas
.
getContext
(
'2d'
)
const
proportion
=
img
.
width
/
img
.
height
const
width
=
maxWidth
const
height
=
maxWidth
/
proportion
console
.
log
(
width
,
height
,
'打印宽高'
)
canvas
.
width
=
width
canvas
.
height
=
height
ctx
.
fillStyle
=
'#fff'
ctx
.
fillRect
(
0
,
0
,
canvas
.
width
,
canvas
.
height
)
ctx
.
drawImage
(
img
,
0
,
0
,
width
,
height
)
const
base64data
=
canvas
.
toDataURL
(
fileType
,
0.75
)
canvas
=
ctx
=
null
return
base64data
const
datasetFields
=
[
'width'
,
'height'
]
const
datasetSizes
:
any
=
{
width
:
'200'
,
height
:
'200'
,
}
datasetFields
.
forEach
((
field
)
=>
{
oImage
.
setAttribute
(
`data-
${
field
}
`
,
datasetSizes
[
field
])
})
oImage
.
src
=
uploadRes
return
oImage
}
const
chooseImg
=
(
e
,
cb
,
maxsize
=
200
*
1024
)
=>
{
const
file
=
e
.
target
.
files
[
0
]
if
(
!
file
||
!
/
\/(?:
jpeg|jpg|png
)
/i
.
test
(
file
.
type
))
{
return
const
getPasteImageFile
=
(
clipboardDataFiles
:
any
)
=>
{
if
(
!
clipboardDataFiles
.
length
)
{
return
null
}
const
reader
=
new
FileReader
()
reader
.
onload
=
function
()
{
const
result
=
this
.
result
let
img
=
new
Image
()
if
(
result
.
length
<=
maxsize
)
{
toPreviewer
(
result
,
cb
)
// 剪切版中选择的(用户第一个点的在尾)第一张图片
const
clipboardDataFileList
=
Array
.
from
(
clipboardDataFiles
||
[])
let
firstSelectedImage
=
null
clipboardDataFileList
.
forEach
((
file
:
any
)
=>
{
if
(
!
file
.
type
.
match
(
/image
\/
/i
))
{
return
}
img
.
onload
=
function
()
{
const
compressedDataUrl
=
compress
(
img
,
file
.
type
,
maxsize
/
1024
)
toPreviewer
(
compressedDataUrl
,
cb
)
img
=
null
firstSelectedImage
=
file
})
return
firstSelectedImage
}
//将文件流转base64
const
fileToBase64
=
function
(
file
:
any
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
const
reader
=
new
FileReader
()
reader
.
onload
=
(
event
:
any
)
=>
{
resolve
(
event
.
target
.
result
)
}
img
.
style
.
width
=
'100px'
img
.
style
.
height
=
'100px'
img
.
src
=
result
reader
.
onerror
=
(
error
)
=>
{
reject
(
error
)
}
reader
.
readAsDataURL
(
file
)
})
}
const
removeDefaultContent
=
function
(
event
:
any
)
{
elAutocomplete
.
value
.
classList
.
remove
(
'textarea'
)
cursorIn
()
}
const
addDefaultContent
=
function
(
event
:
any
)
{
if
(
!
event
.
target
.
innerHTML
&&
event
.
target
.
innerText
.
length
==
0
)
{
elAutocomplete
.
value
.
classList
.
add
(
'textarea'
)
}
}
const
cursorIn
=
function
()
{
var
divElement
:
any
=
document
.
querySelector
(
'div[contenteditable="true"]'
)
var
range
=
document
.
createRange
()
range
.
selectNodeContents
(
divElement
)
range
.
collapse
(
false
)
var
selection
:
any
=
window
.
getSelection
()
selection
.
removeAllRanges
()
selection
.
addRange
(
range
)
}
defineExpose
({
setState
(
res
:
any
)
{
state
.
value
=
res
inputVal
.
value
=
res
},
getState
()
{
return
state
.
value
return
inputVal
.
value
},
})
</
script
>
<
style
lang=
"scss"
scoped
>
.auto-prompt
{
padding
:
20px
;
}
.textarea
{
&:after
{
content
:
attr
(
placeholder
);
display
:
inline-block
;
color
:
#999
;
font-size
:
13px
;
vertical-align
:
middle
;
line-height
:
16px
;
}
}
.icon
{
width
:
24px
;
height
:
24px
;
...
...
src/components/IndexComponent.vue
浏览文件 @
749e555b
...
...
@@ -16,12 +16,14 @@
import
{
ref
,
watch
,
defineEmits
,
defineExpose
,
defineProps
}
from
'vue'
const
list
=
ref
([])
import
*
as
emoji
from
'emoji.json'
console
.
log
(
emoji
,
'emoji'
)
const
getList
=
()
=>
{
list
.
value
=
emoji
.
slice
(
0
,
50
)
list
.
value
.
push
(
emoji
[
55
])
list
.
value
.
push
(
emoji
[
56
])
list
.
value
.
push
(
emoji
[
57
])
list
.
value
.
push
(
emoji
[
60
])
let
info
=
emoji
.
default
list
.
value
=
info
.
slice
(
0
,
50
)
list
.
value
.
push
(
info
[
55
])
list
.
value
.
push
(
info
[
56
])
list
.
value
.
push
(
info
[
57
])
list
.
value
.
push
(
info
[
60
])
}
const
emits
=
defineEmits
([
'emotion'
])
const
clickHandler
=
(
i
:
number
|
string
)
=>
{
...
...
src/utils/websocket.ts
浏览文件 @
749e555b
...
...
@@ -48,7 +48,6 @@ function initWebSocket(wsUrl: string) {
// 定义重连函数
const
reConnect
=
(
wsUrl
:
string
)
=>
{
console
.
log
(
"尝试重新连接"
);
if
(
useUserStore
().
isConnected
)
return
;
// 如果已经连上就不在重连了
rec
&&
clearTimeout
(
rec
);
rec
=
setTimeout
(
function
()
{
...
...
@@ -59,12 +58,12 @@ const reConnect = (wsUrl: string) => {
// 创建连接
function
websocketOpen
()
{
console
.
log
(
"连接成功"
);
useUserStore
().
connect
();
}
// 数据接收
function
websocketonmessage
(
e
:
MessageEvent
<
any
>
)
{
const
res
=
JSON
.
parse
(
e
.
data
);
// 解析JSON格式的数据
console
.
log
(
res
,
"接受到的数据"
);
// 下面的判断则是后台返回的接收到的数据 如何处理自己决定
if
(
res
.
command
==
11
)
{
//将数据放在store中
...
...
@@ -124,5 +123,4 @@ const closeWebSocket = () => {
}
};
export
{
initWebSocket
,
sendWebSocket
,
creatWebSocket
,
closeWebSocket
};
src/views/HomeView copy.vue
deleted
100644 → 0
浏览文件 @
e861a5d9
<
template
>
<div
class=
"common-layout"
>
<el-container>
<el-aside
width=
"280px"
>
<el-header
class=
"el-header-left"
>
<el-input
placeholder=
"搜索"
/>
</el-header>
<div
active-text-color=
"#000"
background-color=
"#fff"
default-active=
"1"
>
<div
class=
"el-menu-item"
:class=
"getMenuItem(item)"
:index=
"item?.userId"
:key=
"item?.userId"
v-for=
"item in userList"
@
click=
"handleMenuClick(item)"
>
<div
class=
"user-info-box"
>
<div
class=
"user-img-box"
>
<img
:src=
"item.userImg"
alt=
""
/>
</div>
<div
class=
"user-info-right"
>
<div
class=
"user-box-right-nbs"
>
<div
class=
"user-name-box"
>
<span
class=
"user-name"
>
{{
item
?.
username
}}
</span>
<div
class=
"user-time"
>
05-19 16:38
</div>
</div>
<div
class=
"user-reply-box"
>
<span
class=
"user-reply"
>
{{
item
?.
messages
[
item
?.
messages
.
length
-
1
]?.
content
.
includes
(
'http'
)
?
'[图片]'
:
item
.
messages
[
item
.
messages
.
length
-
1
]?.
content
}}
</span>
<div
class=
"count"
>
1
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</el-aside>
<el-container
class=
"el-container-center"
v-if=
"customerInfo.username"
>
<el-header
class=
"el-header-center"
>
<p
class=
"name"
>
{{
customerInfo
.
username
}}
</p>
<p
class=
"url"
>
US|https://www.ibeautytop.com
</p>
</el-header>
<el-main
id=
"srollId"
ref=
"srollId"
:style=
"
{
height: '600px',
width: '100%',
border: '1px solid #ccc',
}"
>
<div
class=
"message-container"
v-for=
"(message, index) in messages"
:key=
"index"
:class=
"getMessageClass(message?.isSent)"
>
<div
class=
"time"
>
{{
message
.
time
}}
</div>
<div
v-if=
"message.isSent"
class=
"message-container"
>
<div
class=
"bubble"
>
<div
v-if=
"message?.msgType == 0"
class=
"message"
v-html=
"message?.content"
@
click
.
prevent=
"handleMessageClick($event)"
></div>
<div
v-else
class=
"img-wraper"
>
<el-image
:src=
"message?.content"
:preview-src-list=
"[message?.content]"
/>
</div>
</div>
<div
class=
"avatar"
>
<img
src=
"../assets/logo.png"
alt=
"Avatar"
class=
"avatar-image"
/>
</div>
</div>
<div
v-if=
"!message.isSent"
class=
"message-container"
>
<div
class=
"avatar"
>
<img
:src=
"message?.userImg"
class=
"avatar-image"
/>
</div>
<div
class=
"bubble"
>
<div
v-if=
"message?.msgType == 0"
class=
"message"
v-html=
"message?.content"
@
click
.
prevent=
"handleMessageClick($event)"
></div>
<div
v-else
class=
"img-wraper"
>
<el-image
:src=
"message?.content"
:preview-src-list=
"[message?.content]"
/>
</div>
</div>
</div>
</div>
</el-main>
<el-footer
class=
"el-footer"
>
<AutomaticPrompt
@
keydown
.
enter=
"handleButtonClick"
@
updateState=
"getState"
ref=
"automaticPromptRef"
></AutomaticPrompt>
<el-button
type=
"primary"
plain
class=
"btn-send"
style=
"width: 50px;"
@
click
.
stop=
"handleButtonClick"
>
发送
</el-button>
</el-footer>
</el-container>
<el-container
class=
"el-container-center"
v-else
></el-container>
<el-aside
width=
"400px"
>
<div>
<el-header
class=
"el-header-right"
>
<img
src=
"../assets/logo.png"
alt=
"Avatar"
/>
<div
class=
"name"
>
询价单
</div>
</el-header>
<div
class=
"customer-info user-wrapper"
>
<div
class=
"customer-info-right"
>
<div
class=
"customer-info-box"
>
<div
class=
"avatar-box"
>
<img
src=
"../assets/shop.jpg"
alt=
"Avatar"
/>
</div>
<div
class=
"avatar-right"
>
<div
class=
"avatar-right-name"
>
{{
inquiryInfo
.
name
}}
</div>
<div
class=
"avatar-right-price"
>
<div>
{{
inquiryInfo
.
price
}}
</div>
<div>
{{
inquiryInfo
.
unit
}}
</div>
</div>
<div
class=
"avatar-input-box"
>
<div>
<el-input-number
class=
"avatar-number-input"
:min=
"1"
@
change=
"handleChange"
v-model=
"ruleForm.count"
:max=
"10"
/>
</div>
<div
class=
"avatar-input-right"
>
<div
class=
"change"
>
更换
</div>
<div
class=
"delete"
>
删除
</div>
</div>
</div>
</div>
</div>
<div
class=
"add"
>
<img
src=
"../assets/add-line.png"
/>
<div>
添加
</div>
</div>
</div>
<div
class=
"form"
>
<el-form
ref=
"ruleFormRef"
style=
"max-width: 600px;"
:model=
"ruleForm"
:rules=
"rules"
label-position=
"top"
label-width=
"auto"
>
<el-form-item
label=
"客户名称"
prop=
"customerName"
>
<el-input
v-model=
"ruleForm.customerName"
type=
"password"
placeholder=
"默认展示"
autocomplete=
"off"
/>
</el-form-item>
<el-form-item
label=
"国家/地区"
prop=
"countriesRegions"
>
<el-input
v-model=
"ruleForm.countriesRegions"
type=
"password"
placeholder=
"默认展示"
autocomplete=
"off"
/>
</el-form-item>
<el-form-item
label=
"Email"
prop=
"Email"
>
<el-input
placeholder=
"默认展示"
v-model
.
number=
"ruleForm.Email"
/>
</el-form-item>
<el-form-item
label=
"WatchApp"
prop=
"WatchApp"
>
<el-input
placeholder=
"默认展示"
v-model
.
number=
"ruleForm.WatchApp"
/>
</el-form-item>
<el-form-item
label=
"备注"
prop=
"remark"
>
<el-input
placeholder=
"默认展示"
v-model
.
number=
"ruleForm.remark"
/>
</el-form-item>
<el-form-item
label=
"备注"
prop=
"content"
style=
"margin-bottom: 3px;"
>
<el-input
placeholder=
"文本内容"
v-model=
"ruleForm.content"
type=
"textarea"
:rows=
"8"
/>
</el-form-item>
<el-button
type=
"primary"
plain
style=
"width: 50px;"
@
click
.
stop=
"sumbitFuleForm(ruleFormRef)"
>
发送
</el-button>
</el-form>
</div>
</div>
</div>
</el-aside>
</el-container>
</div>
</
template
>
<
script
lang=
"ts"
setup
>
import
'../assets/font/iconfont.css'
import
{
ElMessage
}
from
'element-plus'
import
{
ref
,
onMounted
,
watch
,
nextTick
,
reactive
}
from
'vue'
import
{
getShortDate
}
from
'../utils/index'
import
{
useUserStore
}
from
'../store/modules/user'
import
AutomaticPrompt
from
'../components/AutomaticPrompt.vue'
import
ImageViewer
from
'@luohc92/vue3-image-viewer'
import
{
sendWebSocket
}
from
'../utils/websocket'
import
type
{
FormInstance
}
from
'element-plus'
import
{
initWebSocket
}
from
"../utils/websocket"
;
import
'@luohc92/vue3-image-viewer/dist/style.css'
import
{
v4
as
uuidv4
}
from
"uuid"
import
{
getUserList
,
InquiryAdd
,
checkMesssages
}
from
'../axios/model/user'
const
ruleForm
=
ref
({
count
:
1
,
})
const
connectMsg
=
()
=>
{
const
useUser
=
useUserStore
();
const
toIp
=
`ws://192.168.31.228:8081?type=yk&code=
${
uuidv4
()}
&kf=10086`
;
console
.
log
(
toIp
)
useUser
.
connect
();
initWebSocket
(
toIp
);
};
connectMsg
();
const
ruleFormRef
=
ref
<
FormInstance
>
()
const
customerInfo
=
ref
({})
const
messages
=
ref
([])
const
count
=
ref
(
0
)
const
store
=
useUserStore
()
const
userList
=
ref
([])
const
automaticPromptRef
=
ref
(
''
)
const
inquiryInfo
=
ref
({
price
:
'20.00'
,
unit
:
'USD'
,
name
:
'Industrial 5V 12V RS232 RS-232 Parallel PCI PC'
,
})
const
rules
=
reactive
({
customerName
:
[
{
required
:
true
,
message
:
'请输入客户名称'
,
trigger
:
'blur'
,
},
],
countriesRegions
:
[
{
required
:
true
,
message
:
'请输入国家/地区'
,
trigger
:
'blur'
,
},
],
Email
:
[
{
required
:
true
,
message
:
'请输入Email'
,
trigger
:
'blur'
,
},
],
WatchApp
:
[
{
required
:
true
,
message
:
'请输入WatchApp'
,
trigger
:
'blur'
,
},
],
remark
:
[
{
required
:
true
,
message
:
'请输入描述'
,
trigger
:
'blur'
,
},
],
content
:
[
{
required
:
true
,
message
:
'请输入描述'
,
trigger
:
'blur'
,
},
],
})
let
msg
=
''
//进入页面直接发送请求从后端获取热点数据
onMounted
(
async
()
=>
{
count
.
value
=
store
.
count
if
(
store
.
userList
.
length
>
0
)
{
userList
.
value
=
store
.
userList
}
else
{
getList
()
}
setMessage
()
})
//设置message
const
setMessage
=
()
=>
{
if
(
store
.
customerInfo
.
username
)
{
customerInfo
.
value
=
store
.
customerInfo
messages
.
value
=
customerInfo
.
value
.
messages
||
[]
}
setSrollHeight
()
}
//获取列表的数据
const
getList
=
async
()
=>
{
let
query
=
{}
try
{
const
{
success
,
result
,
message
}:
any
=
await
getUserList
(
query
)
if
(
success
)
{
let
list
=
result
.
map
((
item
:
any
)
=>
{
item
.
messages
=
[]
return
item
})
userList
.
value
=
list
store
.
setUserList
(
list
)
if
(
customerInfo
.
value
.
username
)
handleMenuClick
(
customerInfo
.
value
)
}
}
catch
(
error
)
{
console
.
log
(
error
)
}
}
//获取子组件中state的值,这个好像是写多余了,可以直接使用automaticPromptRef.value.setState('');获取state值
const
getState
=
(
v
:
string
)
=>
{
msg
=
v
}
const
handleChange
=
(
val
:
any
)
=>
{
store
.
setCount
(
val
)
}
//监听聊天数据的改变
watch
(
userList
,
(
newVal
,
oldVal
)
=>
{
if
(
newVal
&&
newVal
.
length
>
0
&&
customerInfo
.
value
.
username
)
{
const
obj
=
newVal
.
find
((
item
:
any
)
=>
{
return
item
.
username
==
customerInfo
.
value
.
username
})
||
{}
messages
.
value
=
obj
.
messages
||
[]
}
},
{
deep
:
true
},
)
//监听聊天框数据的改变
watch
(
messages
,
(
newVal
,
oldVal
)
=>
{
if
(
newVal
?.
length
>
0
)
setSrollHeight
()
},
{
deep
:
true
},
)
//让聊天滑动窗口滑到底部
const
setSrollHeight
=
()
=>
{
nextTick
(()
=>
{
const
div
=
document
.
getElementById
(
'srollId'
)
if
(
div
)
div
.
scrollTop
=
div
?.
scrollHeight
})
}
//切换聊天人
const
handleMenuClick
=
(
item
:
object
)
=>
{
customerInfo
.
value
=
item
store
.
setCustomerInfo
(
item
)
messages
.
value
=
item
?.
messages
||
[]
const
data
=
{
cmd
:
'19'
,
type
:
'1'
,
fromUserId
:
item
?.
username
,
group_id
:
''
,
userId
:
store
.
userInfo
.
username
,
}
sendWebSocket
(
data
)
getCheckMesssages
(
data
)
}
//发送按钮
const
handleButtonClick
=
()
=>
{
if
(
!
msg
)
{
return
ElMessage
({
message
:
'请输入内容'
,
type
:
'error'
,
})
}
let
data
=
{
content
:
msg
,
isSent
:
true
,
cmd
:
'11'
,
msgType
:
0
,
chatType
:
'2'
,
group_id
:
''
,
time
:
getShortDate
(),
to
:
customerInfo
.
value
.
username
,
form
:
store
.
userInfo
.
username
,
}
messages
.
value
?.
push
(
data
)
sendWebSocket
(
data
)
automaticPromptRef
.
value
.
setState
(
''
)
}
const
handleMessageClick
=
(
event
:
any
)
=>
{
const
target
=
event
.
target
if
(
target
.
tagName
===
'A'
)
{
// 点击的是超链接
// 执行相应的操作
if
(
target
.
innerHTML
===
'解决'
)
{
alert
(
'感谢您的使用'
)
}
else
if
(
target
.
innerHTML
===
'未解决'
)
{
alert
(
'很抱歉未能解决你的问题'
)
}
else
{
handleLinkClick
(
target
.
innerHTML
)
}
}
else
if
(
target
.
tagName
===
'IMG'
)
{
// 点击的图片进行放大操作
ImageViewer
({
//切记额images这个参数是数组,我的target.valueof().src值是一个http的图片地址
images
:
[
target
.
valueOf
().
src
],
curIndex
:
0
,
zIndex
:
2000
,
showDownload
:
true
,
showThumbnail
:
true
,
handlePosition
:
'bottom'
,
maskBgColor
:
'rgba(0,0,0,0.7)'
,
onClose
:
()
=>
{
console
.
log
(
'close'
)
},
})
}
}
//查看消息将其改为已读
const
getCheckMesssages
=
async
(
res
:
any
)
=>
{
try
{
const
{
success
,
result
,
message
}:
any
=
await
checkMesssages
({
username
:
res
.
username
,
})
}
catch
(
error
)
{
console
.
log
(
error
)
}
}
//提交询价单
const
sumbitFuleForm
=
(
formEl
:
FormInstance
|
undefined
)
=>
{
let
query
=
{}
if
(
!
formEl
)
return
formEl
.
validate
(
async
(
valid
:
boolean
)
=>
{
if
(
valid
)
{
try
{
const
{
success
,
result
,
message
}:
any
=
await
InquiryAdd
(
ruleForm
.
value
,
)
if
(
success
)
{
ElMessage
({
message
,
type
:
'success'
,
})
}
}
catch
(
error
)
{
console
.
log
(
error
)
}
}
})
}
const
handleLinkClick
=
(
msg
:
string
)
=>
{
messages
.
value
.
push
({
content
:
msg
,
isSent
:
true
})
}
//消息框样式动态选择
const
getMessageClass
=
(
isSent
:
boolean
)
=>
{
return
isSent
?
'message-container-right'
:
'message-container-left'
}
//左边按钮操作
const
getMenuItem
=
(
item
:
any
)
=>
{
return
item
.
username
==
customerInfo
.
value
?.
username
?
'is-active'
:
''
}
</
script
>
<
style
lang=
"scss"
scoped
>
.el-header-left
{
height
:
76px
;
width
:
100%
;
border
:
1px
solid
#e6e8ed
;
text-align
:
center
;
padding
:
16px
;
.el-input
{
:deep(.el-input__wrapper)
{
background
:
var
(
--Fill-Grey-Fill-1
,
#f8f8fa
);
}
height
:
44px
;
font-family
:
'PingFang SC'
;
font-style
:
normal
;
font-weight
:
400
;
width
:
100
%;
.el-input__inner
{
font-size
:
14px
;
}
}
}
.el-container-center
{
background
:
#e6e8ed
;
}
.el-header-center
{
height
:
76px
;
width
:
100%
;
border
:
1px
solid
#e6e8ed
;
padding
:
16px
24px
;
display
:
flex
;
justify-content
:
space-between
;
flex-direction
:
column
;
.name
{
color
:
#010914
;
font-family
:
'Inter'
;
font-size
:
18px
;
font-style
:
normal
;
font-weight
:
600
;
}
.url
{
color
:
#798494
;
}
}
.el-header-right
{
height
:
76px
;
width
:
100%
;
border
:
1px
solid
#e6e8ed
;
display
:
flex
;
align-items
:
center
;
img
{
width
:
15.42px
;
height
:
17.92px
;
margin-right
:
14px
;
}
.name
{
color
:
#010914
;
font-family
:
'PingFang SC'
;
font-size
:
16px
;
font-style
:
normal
;
font-weight
:
500
;
line-height
:
20px
;
}
}
.el-footer
{
width
:
100%
;
border
:
1px
solid
#ccc
;
border-top
:
0px
;
padding
:
10px
;
height
:
calc
(
100vh
-
600px
-
76px
);
position
:
relative
;
.btn-send
{
position
:
absolute
;
bottom
:
20px
;
right
:
20px
;
}
}
.el-menu-item
{
line-height
:
initial
;
padding
:
0px
!important
;
height
:
86px
;
border-left
:
3px
solid
transparent
;
&.is-active
{
background
:
#eff0f1
;
border-left
:
3px
solid
#1890ff
;
}
}
.user-info-box
{
display
:
flex
;
width
:
100%
;
height
:
100%
;
padding
:
0px
10px
;
box-sizing
:
border-box
;
justify-content
:
space-between
;
.user-img-box
{
margin-right
:
10px
;
display
:
flex
;
align-items
:
center
;
img
{
width
:
40px
;
height
:
40px
;
}
}
.user-info-right
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
height
:
100%
;
width
:
100%
;
.user-box-right-nbs
{
width
:
100%
;
}
.user-reply-box
{
position
:
relative
;
display
:
flex
;
}
.count
{
right
:
16px
;
top
:
4px
;
position
:
absolute
;
display
:
flex
;
width
:
var
(
--Edges-xl
,
16px
);
height
:
var
(
--Edges-xl
,
16px
);
min-width
:
var
(
--Edges-xl
,
16px
);
padding
:
var
(
--Spacing-none
,
0
);
flex-direction
:
column
;
justify-content
:
center
;
color
:
#fff
;
align-items
:
center
;
border-radius
:
var
(
--Radius-full
,
1000px
);
background
:
red
;
}
.label
{
margin-left
:
5px
;
color
:
#3875ea
;
font-size
:
12px
;
background
:
#d8e5ff
;
border-radius
:
2px
;
padding
:
1px
5px
;
&.pc
{
background
:
rgba
(
100
,
64
,
194
,
0.16
);
color
:
#6440c2
;
}
}
}
.user-name-box
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
.user-name
{
word-break
:
break-all
;
text-overflow
:
ellipsis
;
overflow
:
hidden
;
max-width
:
120px
;
font-size
:
16px
;
color
:
rgba
(
0
,
0
,
0
,
0.65
);
margin-right
:
10px
;
font-family
:
'Inter'
;
font-style
:
normal
;
font-weight
:
600
;
}
}
.user-reply
{
display
:
-webkit-box
;
-webkit-box-orient
:
vertical
;
-webkit-line-clamp
:
1
;
overflow
:
hidden
;
color
:
#798494
;
font-feature-settings
:
'clig'
off
,
'liga'
off
;
text-overflow
:
ellipsis
;
font-family
:
'Inter'
;
margin-top
:
3px
;
font-size
:
14px
;
font-style
:
normal
;
font-weight
:
400
;
line-height
:
20px
;
}
.user-time
{
color
:
#999
;
font-size
:
12px
;
font-style
:
normal
;
font-family
:
'Inter'
;
font-weight
:
400
;
}
}
.customer-info-right
{
.customer-info-box
{
display
:
flex
;
.avatar-box
{
width
:
88px
;
height
:
88px
;
border-radius
:
8px
;
margin-right
:
12px
;
overflow
:
hidden
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.avatar-input-box
{
display
:
flex
;
align-items
:
center
;
margin-top
:
8px
;
justify-content
:
space-between
;
.change
{
margin-right
:
27px
;
color
:
#010914
;
text-align
:
right
;
font-family
:
'PingFang SC'
;
font-size
:
12px
;
font-style
:
normal
;
font-weight
:
400
;
}
.delete
{
color
:
#ff6600
;
text-align
:
right
;
font-family
:
'PingFang SC'
;
font-size
:
12px
;
font-style
:
normal
;
font-weight
:
400
;
}
}
.avatar-input-right
{
display
:
flex
;
}
.avatar-right
{
flex
:
1
;
.avatar-right-name
{
display
:
-webkit-box
;
-webkit-box-orient
:
vertical
;
-webkit-line-clamp
:
1
;
flex
:
1
0
0
;
overflow
:
hidden
;
color
:
#010914
;
font-feature-settings
:
'clig'
off
,
'liga'
off
;
text-overflow
:
ellipsis
;
font-family
:
'Inter'
;
font-size
:
14px
;
font-style
:
normal
;
font-weight
:
400
;
line-height
:
20px
;
}
.avatar-right-price
{
display
:
flex
;
color
:
#010914
;
font-feature-settings
:
'clig'
off
,
'liga'
off
;
font-family
:
'Inter'
;
font-size
:
16px
;
font-style
:
normal
;
line-height
:
24px
;
div
{
&:nth-of-type(1)
{
margin-right
:
4px
;
font-weight
:
600
;
}
&
:nth-of-type
(
2
)
{
font-weight
:
400
;
}
}
}
}
}
}
.el-input__wrapper
{
width
:
48px
;
}
.user-wrapper
{
padding
:
16px
;
}
.user-info
{
padding-top
:
15px
;
padding-bottom
:
10px
;
}
.chat-bar
{
height
:
50px
;
width
:
100%
;
line-height
:
50px
;
text-align
:
left
;
}
.send-btn
{
position
:
absolute
;
right
:
0
;
bottom
:
10px
;
display
:
-webkit-box
;
display
:
-ms-flexbox
;
display
:
flex
;
-webkit-box-pack
:
end
;
-ms-flex-pack
:
end
;
justify-content
:
flex-end
;
margin-top
:
10px
;
margin-right
:
10px
;
}
.underline-link
{
text-decoration
:
underline
;
}
.message-container
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
10px
;
position
:
relative
;
}
.message-container
.time
{
text-align
:
center
;
color
:
#999
;
font-size
:
14px
;
position
:
absolute
;
width
:
100%
;
}
.avatar
{
margin-left
:
10px
;
/* 修改这里将头像放在消息框的右边 */
}
.avatar-image
{
width
:
40px
;
height
:
40px
;
border-radius
:
50%
;
object-fit
:
cover
;
}
.bubble
{
background-color
:
#e8e8e8
;
color
:
#000
;
padding
:
10px
;
border-radius
:
5px
;
max-width
:
320px
;
background
:
#f5f5f5
;
border-radius
:
10px
;
color
:
#000
;
font-size
:
14px
;
overflow
:
hidden
;
.img-wraper
img
{
max-width
:
100%
;
height
:
auto
;
display
:
block
;
}
}
.message
{
text-align
:
left
;
margin
:
0
;
}
.message-container-right
{
justify-content
:
flex-end
;
}
.message-container-left
{
justify-content
:
flex-start
;
}
.avatar-number-input
:deep
(
.el-input
)
{
width
:
102px
!important
;
}
.avatar-number-input
{
width
:
102px
!important
;
}
.add
{
display
:
flex
;
height
:
var
(
--Layout-lg
,
32px
);
padding
:
0
var
(
--Spacing-lg
,
12px
)
0
var
(
--Spacing-md
,
8px
);
justify-content
:
center
;
align-items
:
center
;
gap
:
4px
;
border-radius
:
var
(
--Radius-xs
,
4px
);
border
:
var
(
--Edges-zero
,
1px
)
solid
var
(
--color-Stroke-Weak
,
#e6e8ed
);
background
:
var
(
--color-bg-Program-White
,
#fff
);
width
:
72px
;
height
:
32px
;
margin-top
:
16px
;
img
{
width
:
20px
;
height
:
20px
;
}
}
.form
{
margin-top
:
32px
;
:deep(.el-form-item__label)
{
color
:
#010914
;
font-family
:
'PingFang SC'
;
font-size
:
14px
;
font-style
:
normal
;
font-weight
:
bolder
;
line-height
:
20px
;
}
:deep
(
.el-input__wrapper
)
{
padding
:
0px
;
}
:deep
(
.el-input__inner
)
{
padding
:
8px
;
display
:
-webkit-box
;
-webkit-box-orient
:
vertical
;
-webkit-line-clamp
:
1
;
flex
:
1
0
0
;
overflow
:
hidden
;
color
:
#798494
;
font-feature-settings
:
'clig'
off
,
'liga'
off
;
text-overflow
:
ellipsis
;
font-family
:
'PingFang SC'
;
font-size
:
14px
;
font-style
:
normal
;
font-weight
:
400
;
line-height
:
20px
;
background
:
#f2f3f5
;
}
:deep
(
.el-textarea__inner
)
{
background
:
#f2f3f5
;
}
}
</
style
>
src/views/HomeView.vue
浏览文件 @
749e555b
...
...
@@ -3,18 +3,18 @@
<el-container>
<el-container
class=
"el-container-center"
v-if=
"customerInfo.username"
>
<el-header
class=
"el-header-center"
>
<p
class=
"name"
>
{{
customerInfo
.
username
}}
</p>
<p
class=
"url"
>
US|https://www.ibeautytop.com
</p>
<p
class=
"onlineCustomer"
>
您正在与 在线客服 对话
</p>
</el-header>
<el-main
id=
"srollId"
ref=
"srollId"
:style=
"
{
height: '60
0px
',
height: '60
vh
',
width: '100%',
border: '1px solid #ccc',
}"
>
<div
class=
"title"
>
您好,系统正在为您接入在线客服
</div>
<div
class=
"message-container"
v-for=
"(message, index) in messages"
...
...
@@ -67,20 +67,36 @@
</div>
</el-main>
<el-footer
class=
"el-footer"
>
<div
class=
"static-box"
>
<div
v-for=
"(item, index) in staticList"
:key=
"index"
class=
"staticList"
>
<div>
{{
item
}}
</div>
</div>
</div>
<AutomaticPrompt
@
keydown
.
enter=
"handleButtonClick"
@
updateState=
"getState"
ref=
"automaticPromptRef"
></AutomaticPrompt>
<div
class=
"btn-send"
>
<el-button
type=
"primary"
plain
class=
"btn-send"
style=
"width: 50px;"
style=
"width: 50px; background: #f8f8fa; color: #000;"
@
click
.
stop=
"handleButtonClick"
>
发送
Finish
</el-button>
<el-button
style=
"width: 50px; color: #fff; background: #000;"
plain
@
click
.
stop=
"handleButtonClick"
>
send
</el-button>
</div>
</el-footer>
</el-container>
<el-container
class=
"el-container-center"
v-else
></el-container>
...
...
@@ -90,95 +106,41 @@
<
script
lang=
"ts"
setup
>
import
'../assets/font/iconfont.css'
import
{
ElMessage
}
from
'element-plus'
import
{
ref
,
onMounted
,
watch
,
nextTick
,
reactive
,
getCurrentInstance
,
}
from
'vue'
import
{
ref
,
onMounted
,
watch
,
nextTick
,
getCurrentInstance
}
from
'vue'
import
{
getShortDate
}
from
'../utils/index'
import
{
useUserStore
}
from
'../store/modules/user'
import
AutomaticPrompt
from
'../components/AutomaticPrompt.vue'
import
ImageViewer
from
'@luohc92/vue3-image-viewer'
import
{
sendWebSocket
}
from
'../utils/websocket'
import
type
{
FormInstance
}
from
'element-plus'
import
{
initWebSocket
}
from
'../utils/websocket'
import
'@luohc92/vue3-image-viewer/dist/style.css'
import
{
v4
as
uuidv4
}
from
'uuid'
import
{
getUserList
,
InquiryAdd
,
checkMesssages
}
from
'../axios/model/user'
const
ruleForm
=
ref
({
count
:
1
,
})
const
{
appContext
}
=
getCurrentInstance
()
const
ruleFormRef
=
ref
<
FormInstance
>
()
import
{
checkMesssages
}
from
'../axios/model/user'
const
customerInfo
=
ref
({})
const
messages
=
ref
([])
const
count
=
ref
(
0
)
const
staticList
=
ref
([
'Login account'
,
'Forgot password'
,
'Free entry'
,
'Add/manage products'
,
'improve'
,
])
const
store
=
useUserStore
()
const
automaticPromptRef
=
ref
(
''
)
const
rules
=
reactive
({
customerName
:
[
{
required
:
true
,
message
:
'请输入客户名称'
,
trigger
:
'blur'
,
},
],
countriesRegions
:
[
{
required
:
true
,
message
:
'请输入国家/地区'
,
trigger
:
'blur'
,
},
],
Email
:
[
{
required
:
true
,
message
:
'请输入Email'
,
trigger
:
'blur'
,
},
],
WatchApp
:
[
{
required
:
true
,
message
:
'请输入WatchApp'
,
trigger
:
'blur'
,
},
],
remark
:
[
{
required
:
true
,
message
:
'请输入描述'
,
trigger
:
'blur'
,
},
],
content
:
[
{
required
:
true
,
message
:
'请输入描述'
,
trigger
:
'blur'
,
},
],
})
let
msg
=
''
//进入页面直接发送请求从后端获取热点数据
onMounted
(
async
()
=>
{
connectMsg
().
then
((
res
)
=>
{
// const translate = appContext.config.globalProperties.$translate
getHistoryMessage
()
setMessage
()
})
})
const
connectMsg
=
()
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
return
new
Promise
((
resolve
:
any
,
reject
:
any
)
=>
{
const
useUser
=
useUserStore
()
useUser
.
setUserInfo
({
username
:
Date
.
now
(),
})
const
toIp
=
`ws://192.168.31.228:8081?type=yk&code=
${
useUser
.
userInfo
.
username
}
&kf=
${
store
.
customerInfo
.
username
}
`
console
.
log
(
toIp
)
const
toIp
=
`ws://192.168.31.123:8081?type=yk&code=
${
useUser
.
userInfo
.
username
}
&kf=
${
store
.
customerInfo
.
username
}
`
useUser
.
connect
()
initWebSocket
(
toIp
)
setTimeout
(()
=>
{
...
...
@@ -195,8 +157,9 @@ const getHistoryMessage = () => {
group_id
:
''
,
userId
:
store
.
userInfo
.
username
,
}
console
.
log
(
data
)
sendWebSocket
(
data
)
getCheckMesssages
(
data
)
//
getCheckMesssages(data)
}
//设置message
...
...
@@ -248,7 +211,6 @@ const handleButtonClick = () => {
to
:
customerInfo
.
value
.
username
,
form
:
store
.
userInfo
.
username
,
}
console
.
log
(
'打印用户发送的内容'
,
data
)
messages
.
value
?.
push
(
data
)
sendWebSocket
(
data
)
automaticPromptRef
.
value
.
setState
(
''
)
...
...
@@ -301,6 +263,32 @@ const getMessageClass = (isSent: boolean) => {
}
</
script
>
<
style
lang=
"scss"
scoped
>
.title
{
display
:
flex
;
padding
:
8px
;
justify-content
:
center
;
align-items
:
center
;
border-radius
:
4px
;
width
:
196px
;
background
:
#f8f8fa
;
font-size
:
12px
;
font-family
:
'PingFang SC'
;
font-weight
:
400
;
margin
:
0px
auto
;
color
:
#798494
;
}
.onlineCustomer
{
height
:
44px
;
padding
:
8px
;
line-height
:
44px
;
align-items
:
center
;
gap
:
8px
;
background
:
rgba
(
248
,
248
,
250
,
1
);
font-family
:
'PingFang SC'
;
font-size
:
14px
;
font-style
:
normal
;
font-weight
:
600
;
}
.el-header-left
{
height
:
76px
;
width
:
100%
;
...
...
@@ -322,13 +310,13 @@ const getMessageClass = (isSent: boolean) => {
}
}
.el-container-center
{
background
:
#e6e8ed
;
background
:
#fff
;
}
.el-header
{
padding
:
0px
;
}
.el-header-center
{
height
:
76px
;
width
:
100%
;
border
:
1px
solid
#e6e8ed
;
padding
:
16px
24px
;
display
:
flex
;
justify-content
:
space-between
;
flex-direction
:
column
;
...
...
@@ -367,14 +355,35 @@ const getMessageClass = (isSent: boolean) => {
width
:
100%
;
border
:
1px
solid
#ccc
;
border-top
:
0px
;
padding
:
1
0px
;
height
:
calc
(
100vh
-
60
0px
-
76
px
);
padding
:
0px
;
height
:
calc
(
100vh
-
60
vh
-
60
px
);
position
:
relative
;
.btn-send
{
position
:
absolute
;
bottom
:
20px
;
right
:
20px
;
}
.static-box
{
display
:
flex
;
background
:
#e6e8ed
;
padding
:
8px
16px
;
}
.staticList
{
height
:
36px
;
border
:
1px
solid
#e6e8ed
;
border-radius
:
36px
;
background
:
#fff
;
width
:
max-width
;
color
:
#000000
;
padding
:
0px
16px
;
font-family
:
'Inter'
;
font-size
:
14px
;
font-weight
:
600
;
line-height
:
36px
;
text-align
:
center
;
margin-right
:
8px
;
}
}
.el-menu-item
{
line-height
:
initial
;
...
...
@@ -452,7 +461,7 @@ const getMessageClass = (isSent: boolean) => {
word-break
:
break-all
;
text-overflow
:
ellipsis
;
overflow
:
hidden
;
max-width
:
12
0px
;
max-width
:
8
0px
;
font-size
:
16px
;
color
:
rgba
(
0
,
0
,
0
,
0.65
);
margin-right
:
10px
;
...
...
@@ -463,10 +472,9 @@ const getMessageClass = (isSent: boolean) => {
}
.user-reply
{
display
:
-webkit-box
;
-webkit-box-orient
:
vertical
;
-webkit-line-clamp
:
1
;
word-break
:
break-all
;
text-overflow
:
ellipsis
;
max-width
:
80px
;
overflow
:
hidden
;
color
:
#798494
;
font-feature-settings
:
'clig'
off
,
'liga'
off
;
...
...
@@ -638,6 +646,8 @@ const getMessageClass = (isSent: boolean) => {
color
:
#000
;
font-size
:
14px
;
overflow
:
hidden
;
word-break
:
break-all
;
text-overflow
:
ellipsis
;
.img-wraper
img
{
max-width
:
100%
;
height
:
auto
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论