Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
T
tokcos-socket-web-ts
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
小豪
tokcos-socket-web-ts
Commits
3ac1e7e0
提交
3ac1e7e0
authored
3月 22, 2024
作者:
Hao
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
add
父级
2b11e564
隐藏空白字符变更
内嵌
并排
正在显示
7 个修改的文件
包含
150 行增加
和
1262 行删除
+150
-1262
src/axios/index.ts
+5
-5
src/axios/model/user.ts
+7
-8
src/minxins/minxin.js
+0
-23
src/store/modules/user.ts
+24
-51
src/utils/websocket.ts
+6
-7
src/views/HomeView-1.vue
+0
-1022
src/views/HomeView.vue
+108
-146
没有找到文件。
src/axios/index.ts
浏览文件 @
3ac1e7e0
// axios.js
import
{
useUserStore
}
from
'../store/modules/user'
import
axios
from
"axios"
;
const
instance
=
axios
.
create
({
//
baseURL: "http://192.168.31.228:8080", // 设置基础 URL
baseURL
:
''
,
baseURL
:
"http://192.168.31.228:8080"
,
// 设置基础 URL
//
baseURL:'',
timeout
:
1000
,
// 设置请求超时时间
});
instance
.
interceptors
.
request
.
use
(
(
config
:
any
)
=>
{
// 在发送请求之前做些什么,例如添加 token
const
token
=
localStorage
.
getItem
(
"token"
)
;
const
token
=
useUserStore
().
userInfo
.
token
;
if
(
token
)
{
config
.
headers
[
'
X-Access-Token'
]
=
`Bearer
${
token
}
`
;
config
.
headers
[
'
token'
]
=
`
${
useUserStore
().
userInfo
.
token
}
`
;
}
return
config
;
},
...
...
src/axios/model/user.ts
浏览文件 @
3ac1e7e0
...
...
@@ -6,7 +6,6 @@ const Login = (query: any) => {
method
:
"post"
,
data
:
query
,
});
};
//新增询价单
const
InquiryAdd
=
(
query
:
any
)
=>
{
...
...
@@ -27,17 +26,17 @@ const Kjiehuifu = (query: any) => {
//获取用户列表
const
getUserList
=
(
query
:
any
)
=>
{
return
http
({
url
:
"/
getUser
List"
,
url
:
"/
kf/chat/getChat
List"
,
method
:
"get"
,
data
:
query
,
params
:
query
,
});
};
//查看未读消息
const
checkMesssages
=
(
query
:
any
)
=>
{
return
http
({
url
:
"/
checkMesssages
"
,
url
:
"/
kf/chat/getChatHisList
"
,
method
:
"get"
,
data
:
query
,
params
:
query
,
});
};
//上传接口
...
...
@@ -58,11 +57,11 @@ const upload = (query: any) => {
});
};
//删除
const
deleteUserList
=
(
query
:
any
)
=>
{
const
deleteUserList
=
(
query
:
any
)
=>
{
return
http
({
url
:
`/deleteUserList`
,
method
:
"delete"
,
params
:
query
params
:
query
,
});
};
export
{
...
...
@@ -73,5 +72,5 @@ export {
checkMesssages
,
upload
,
getUploadConfigInfo
,
deleteUserList
deleteUserList
,
};
src/minxins/minxin.js
deleted
100644 → 0
浏览文件 @
2b11e564
// import io from 'socket.io-client';
// import { ref, onMounted, onUnmounted } from 'vue'
// import { ElMessage } from 'element-plus'
// export default function () {
// const message = {};
// const showMessage = {};
// const socket = ref(new WebSocket("ws://192.168.31.228:8888?username=admin&password=123"));
// socket.value.onopen = function (e) {
// console.log(e)
// }
// socket.value.onerror = function(e){
// console.log(e)
// };
// socket.value.onmessage = function (e) {
// console.log(e)
// };
// return {
// message,
// showMessage
// }
// }
\ No newline at end of file
src/store/modules/user.ts
浏览文件 @
3ac1e7e0
import
moment
from
"moment"
;
import
{
defineStore
}
from
"pinia"
;
import
{
getUserList
}
from
"@/axios/model/user"
;
export
const
useUserStore
=
defineStore
(
"user"
,
{
state
:
()
=>
{
return
{
...
...
@@ -9,8 +10,8 @@ export const useUserStore = defineStore("user", {
count
:
0
,
userInfo
:
{
username
:
"admin"
,
password
:
""
,
token
:
""
password
:
""
,
token
:
""
,
},
userList
:
[],
};
...
...
@@ -25,57 +26,29 @@ export const useUserStore = defineStore("user", {
setUserInfo
(
res
:
any
)
{
this
.
userInfo
=
res
;
},
setUserList
(
res
:
any
)
{
this
.
userList
=
res
;
},
//
setUserList(res: any) {
//
this.userList = res;
//
},
setUserListMessages
(
res
:
any
)
{
console
.
log
(
res
)
const
userList
:
any
=
this
.
userList
||
[];
// res.from = res.from.replace('游客','')
const
obj
=
{
...
res
,
isSent
:
false
,
userImg
:
"https://cdn.lirimall.com//lirigo/filetempImage/新鲜水果_1661668973048.png"
,
time
:
moment
(
res
.
createTime
).
format
(
"YYYY-DD-MM HH:mm:ss"
),
};
const
info
=
{
userId
:
res
.
id
,
username
:
res
.
from
,
messages
:
[
obj
],
userImg
:
obj
.
userImg
,
};
if
(
userList
.
length
>
0
&&
userList
.
some
((
item
:
any
)
=>
item
.
username
==
res
.
from
)
)
{
userList
.
forEach
((
item
:
any
)
=>
{
if
(
item
.
username
==
res
.
from
)
item
.
messages
.
push
(
obj
);
});
}
else
{
userList
.
push
(
info
);
}
this
.
userList
=
userList
;
},
setusernameMessage
(
res
:
any
)
{
const
friends
=
res
.
friends
;
for
(
const
key
in
friends
)
{
const
chatDatas
=
friends
[
key
];
chatDatas
.
forEach
((
item
:
any
)
=>
{
item
.
userImg
=
"https://cdn.lirimall.com//lirigo/filetempImage/新鲜水果_1661668973048.png"
;
item
.
time
=
moment
(
res
.
createTime
).
format
(
"YYYY-DD-MM HH:mm:ss"
);
console
.
log
(
"调取setUserListMessages"
);
return
new
Promise
((
resolve
,
reject
)
=>
{
const
query
=
{
code
:
this
.
userInfo
.
username
,
};
getUserList
(
query
).
then
(({
success
,
result
,
message
}:
any
)
=>
{
if
(
success
)
{
this
.
userList
=
result
.
map
((
item
:
any
)
=>
{
return
{
...
item
,
username
:
item
.
id
,
messages
:
[],
};
});
console
.
log
(
this
.
userList
);
resolve
(
result
);
}
});
for
(
const
index
in
chatDatas
)
{
const
userId
=
chatDatas
[
index
].
from
;
this
.
userList
.
forEach
((
item
:
any
)
=>
{
if
(
item
&&
item
?.
username
==
userId
)
{
item
.
messages
=
chatDatas
;
}
});
}
}
});
},
connect
()
{
// 连接成功后,将 isConnected 状态设置为 true
...
...
src/utils/websocket.ts
浏览文件 @
3ac1e7e0
...
...
@@ -26,7 +26,7 @@ function creatWebSocket(wsUrl: string) {
// 初始化websocket
function
initWebSocket
(
wsUrl
:
string
)
{
if
(
!
websocket
)
websocket
=
new
WebSocket
(
wsUrl
);
websocket
.
onopen
=
function
()
{
websocket
.
onopen
=
function
(
eee
)
{
websocketOpen
();
};
// // 接收
...
...
@@ -64,14 +64,14 @@ function websocketOpen() {
}
// 数据接收
function
websocketonmessage
(
e
:
MessageEvent
<
any
>
)
{
console
.
log
(
'接受数据'
,
e
.
data
)
const
res
=
JSON
.
parse
(
e
.
data
);
// 解析JSON格式的数据
// 下面的判断则是后台返回的接收到的数据 如何处理自己决定
console
.
log
(
res
,
'res'
)
if
(
res
.
command
==
11
)
{
//将数据放在store中
useUserStore
().
setUserListMessages
(
res
.
data
);
}
else
if
(
res
.
command
==
20
)
{
useUserStore
().
setusernameMessage
(
res
.
data
);
// 下面的判断则是后台返回的接收到的数据 如何处理自己决定
useUserStore
()
.
setUserListMessages
(
res
.
data
)
.
then
();
}
}
...
...
@@ -82,7 +82,6 @@ function websocketclose(e: any) {
// 数据发送
function
websocketsend
(
res
:
any
)
{
console
.
log
(
websocket
,
"websocket"
);
if
(
websocket
&&
useUserStore
().
isConnected
)
{
// 检查连接状态
console
.
log
(
"发送的数据"
,
websocket
);
...
...
src/views/HomeView-1.vue
deleted
100644 → 0
浏览文件 @
2b11e564
<
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"
>
{{
item
?.
time
}}
</div>
</div>
<div
class=
"user-reply-box"
>
<span
class=
"user-reply"
>
{{
item
?.
messages
[
item
?.
messages
.
length
-
1
]?.
content
.
includes
(
'img'
)
?
'[图文]'
:
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"
@
contextmenu=
"showContextMenu1($event, item)"
>
<div
v-if=
"message?.msgType == 0"
class=
"message"
>
<div
class=
"content"
v-html=
"message?.content"
></div>
<div
class=
"contentFy"
v-html=
"message?.contentFy"
></div>
</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"
@
contextmenu=
"showContextMenu1($event, item)"
>
<div
v-if=
"message?.msgType == 0"
class=
"message"
>
<div
class=
"content"
v-html=
"message?.content"
></div>
<div
class=
"contentFy"
v-html=
"message?.contentFy"
></div>
</div>
<div
v-else
class=
"img-wraper"
>
<el-image
:src=
"message?.content"
:preview-src-list=
"[message?.content]"
/>
</div>
</div>
</div>
</div>
<RightClickMenu
:top=
"contextMenuTop"
:left=
"contextMenuLeft"
:active=
"contextMenuIndex"
:handleItemClick=
"handleContextMenuItemClick"
:visible=
"contextMenuVisible"
ref=
"targetElement"
:menuList=
"menuList"
/>
</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
{
getUserList
,
InquiryAdd
,
checkMesssages
}
from
'../axios/model/user'
import
{
openRightClickMenu
}
from
'@/components/Menu/RightClickMenu'
import
RightClickMenu
from
'@/components/RightClickMenu.vue'
let
{
showContextMenu1
,
hideContextMenu2
}
=
openRightClickMenu
()
let
menuList
=
ref
([
'复制'
,
'翻译'
,
'设为客户名称'
,
'设置国家/地区'
,
'设为Email'
,
'设为watchApp'
,
])
const
targetElement
=
ref
(
null
)
const
contextMenuVisible
=
ref
(
false
)
const
contextMenuTop
=
ref
(
0
)
const
contextMenuLeft
=
ref
(
0
)
const
contextMenuIndex
=
ref
(
0
)
//显示右键菜单的函数,在右键点击时调用。
//它阻止了默认的右键菜单并设置了菜单的位置,同时添加了一个点击事件监听器来在其他地方点击时隐藏菜单。
const
showContextMenu
=
(
event
,
item
)
=>
{
//阻止默认右键事件
event
.
preventDefault
()
//菜单可见
contextMenuVisible
.
value
=
true
//获取鼠标坐标
contextMenuTop
.
value
=
event
.
clientY
contextMenuLeft
.
value
=
event
.
clientX
// 检测是否超出浏览器视口区域 一个菜单40px*6个
const
menuHeight
=
300
// 右键菜单的高度
const
windowHeight
=
window
.
innerHeight
if
(
event
.
clientY
+
menuHeight
>
windowHeight
)
{
// 如果右键菜单超出视口底部,调整位置为向上弹出
contextMenuTop
.
value
-=
menuHeight
}
window
.
addEventListener
(
'click'
,
hideContextMenu
)
}
//隐藏右键菜单的函数,移除点击事件监听器,并将 contextMenuVisible 设置为 false
const
hideContextMenu
=
(
e
:
any
)
=>
{
var
pop
:
any
=
document
.
querySelector
(
'#select-box'
)
if
(
!
e
||
(
e
?.
target
&&
!
pop
.
contains
(
e
?.
target
)))
{
contextMenuVisible
.
value
=
false
window
.
removeEventListener
(
'click'
,
hideContextMenu
)
}
}
//处理右键菜单项点击的函数,你可以在这里处理点击菜单项后的逻辑。
const
handleContextMenuItemClick
=
(
item
:
any
,
index
:
number
)
=>
{
console
.
log
(
`Clicked on
${(
item
,
index
)}
`
)
contextMenuIndex
.
value
=
index
hideContextMenu
()
}
const
ruleForm
=
ref
({
count
:
1
,
})
const
connectMsg
=
()
=>
{
const
useUser
=
useUserStore
()
const
toIp
=
`ws://192.168.31.228:8081?type=kf&code=
${
useUser
.
userInfo
?.
username
}
`
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(() => {
// count.value = store.count
// userList.value = store.userList
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 = result
store.setUserList(result)
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: any = {
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: any = {
content: msg,
contentFy: msg,
isSent: true,
cmd: '11',
msgType: 0,
fromLang: 'cn',
toLang: 'en',
chatType: '2',
group_id: '',
time: getShortDate(),
to: customerInfo.value.username,
form: store.userInfo.username,
}
messages.value?.push(data)
console.log('打印客户发送的内容', 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 showContextMenu = (e: any, item: any) => {
// e.preventDefault()
// const { closeMenu, openMenu, isShow } = openCountextMenus(e)
// if (isShow) closeMenu()
// openMenu(e)
// }
//提交询价单
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
:
#f8f8fa
;
}
.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
14px
;
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
;
.content
{
font-family
:
'Inter'
;
font-size
:
16px
;
font-style
:
normal
;
font-weight
:
400
;
color
:
#0c203d
;
margin-bottom
:
8px
;
}
.contentFy
{
font-family
:
'PingFang SC'
;
font-size
:
14px
;
font-style
:
normal
;
font-weight
:
400
;
color
:
#798494
;
line-height
:
20px
;
}
}
.message-container-right
{
justify-content
:
flex-end
;
.bubble
{
background
:
#e6e8eb
;
border-radius
:
8px
0
8px
8px
;
}
}
.message-container-left
{
justify-content
:
flex-start
;
.bubble
{
background
:
#ffffff
;
border-radius
:
0px
8px
8px
8px
;
margin-left
:
8px
;
}
}
.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
浏览文件 @
3ac1e7e0
...
...
@@ -5,17 +5,13 @@
<el-header
class=
"el-header-left"
>
<el-input
placeholder=
"搜索"
/>
</el-header>
<div
active-text-color=
"#000"
background-color=
"#fff"
default-active=
"1"
>
<div
active-text-color=
"#000"
background-color=
"#fff"
>
<div
class=
"el-menu-item"
:class=
"getMenuItem(item)"
:index=
"item?.
userI
d"
:key=
"item?.
userI
d"
v-for=
"item in
userL
ist"
:index=
"item?.
i
d"
:key=
"item?.
i
d"
v-for=
"item in
l
ist"
@
click=
"handleMenuClick(item)"
>
<div
class=
"user-info-box"
>
...
...
@@ -25,20 +21,14 @@
<div
class=
"user-info-right"
>
<div
class=
"user-box-right-nbs"
>
<div
class=
"user-name-box"
>
<span
class=
"user-name"
>
{{
item
?.
username
}}
</span>
<span
class=
"user-name"
>
{{
item
?.
nick
}}
</span>
<div
class=
"user-time"
>
{{
item
?.
t
ime
}}
{{
item
?.
createT
ime
}}
</div>
</div>
<div
class=
"user-reply-box"
>
<span
class=
"user-reply"
>
{{
item
?.
messages
[
item
?.
messages
.
length
-
1
]?.
content
.
includes
(
'img'
)
?
'[图文]'
:
item
.
messages
[
item
.
messages
.
length
-
1
]?.
content
}}
{{
item
.
content
}}
</span>
<div
class=
"count"
>
1
</div>
</div>
...
...
@@ -48,10 +38,12 @@
</div>
</div>
</el-aside>
<el-container
class=
"el-container-center"
v-if=
"cu
stomer
Info.username"
>
<el-container
class=
"el-container-center"
v-if=
"cu
rrent
Info.username"
>
<el-header
class=
"el-header-center"
>
<p
class=
"name"
>
{{
customerInfo
.
username
}}
</p>
<p
class=
"url"
>
US|https://www.ibeautytop.com
</p>
<p
class=
"name"
>
{{
currentInfo
.
nick
}}
</p>
<p
class=
"url"
>
{{
currentInfo
.
sessionId
}}
|
{{
currentInfo
.
orginUrl
}}
</p>
</el-header>
<el-main
id=
"srollId"
...
...
@@ -66,10 +58,10 @@
class=
"message-container"
v-for=
"(message, index) in messages"
:key=
"index"
:class=
"getMessageClass(message
?.isSent
)"
:class=
"getMessageClass(message)"
>
<div
class=
"time"
>
{{
message
.
t
ime
}}
</div>
<div
v-if=
"
message.isSent
"
class=
"message-container"
>
<div
class=
"time"
>
{{
message
.
createT
ime
}}
</div>
<div
v-if=
"
isSelf(message)
"
class=
"message-container"
>
<div
class=
"bubble"
@
contextmenu=
"showContextMenu1($event, item)"
>
<div
v-if=
"message?.msgType == 0"
class=
"message"
>
<div
class=
"content"
v-html=
"message?.content"
></div>
...
...
@@ -90,7 +82,7 @@
/>
</div>
</div>
<div
v-if=
"!
message.isSent
"
class=
"message-container"
>
<div
v-if=
"!
isSelf(message)
"
class=
"message-container"
>
<div
class=
"avatar"
>
<img
:src=
"message?.userImg"
class=
"avatar-image"
/>
</div>
...
...
@@ -243,7 +235,7 @@
<
script
lang=
"ts"
setup
>
import
'../assets/font/iconfont.css'
import
{
ElMessage
}
from
'element-plus'
import
{
ref
,
onMounted
,
watch
,
nextTick
,
reactive
}
from
'vue'
import
{
ref
,
onMounted
,
watch
,
nextTick
,
reactive
,
computed
}
from
'vue'
import
{
getShortDate
}
from
'../utils/index'
import
{
useUserStore
}
from
'../store/modules/user'
import
AutomaticPrompt
from
'@/components/AutomaticPrompt.vue'
...
...
@@ -254,31 +246,18 @@ import { initWebSocket } from '../utils/websocket'
import
'@luohc92/vue3-image-viewer/dist/style.css'
import
{
getUserList
,
InquiryAdd
,
checkMesssages
}
from
'../axios/model/user'
import
useMenu
from
'@/components/Menu/RightClickMenu'
import
RightClickMenu
from
'@/components/RightClickMenu.vue'
let
{
showContextMenu1
,
hideContextMenu2
}
=
useMenu
()
// let { showContextMenu1, hideContextMenu2 } = openRightClickMenu()
let
{
showContextMenu1
,
hideContextMenu2
}
=
useMenu
()
//显示右键菜单的函数,在右键点击时调用。
//它阻止了默认的右键菜单并设置了菜单的位置,同时添加了一个点击事件监听器来在其他地方点击时隐藏菜单。
//处理右键菜单项点击的函数,你可以在这里处理点击菜单项后的逻辑。
const
ruleForm
=
ref
({
count
:
1
,
})
const
connectMsg
=
()
=>
{
const
useUser
=
useUserStore
()
const
toIp
=
`ws://192.168.31.228:8081?type=kf&code=
${
useUser
.
userInfo
?.
username
}
`
console.log(toIp)
useUser.connect()
initWebSocket(toIp)
}
// connectMsg()
const
ruleFormRef
=
ref
<
FormInstance
>
()
const customerInfo = ref({})
const
messages
=
ref
([])
const count = ref(0
)
const
s
tore = useUserStore()
const
userL
ist = ref([])
// const { getUseUserStore.userInfo, setcurrentInfo, userList } = useUserStore(
)
const
getUseUserS
tore
=
useUserStore
()
const
l
ist
=
ref
([])
const
automaticPromptRef
=
ref
(
''
)
const
inquiryInfo
=
ref
({
price
:
'20.00'
,
...
...
@@ -331,68 +310,69 @@ const rules = reactive({
})
let
msg
=
''
//进入页面直接发送请求从后端获取热点数据
let
currentInfo
=
ref
({
messages
:
[],
username
:
''
,
})
const
connectMsg
=
()
=>
{
const
useUser
=
useUserStore
()
const
toIp
=
`ws://192.168.31.228:8081?type=kf&code=
${
getUseUserStore
.
userInfo
?.
username
}
`
useUser.connect()
initWebSocket(toIp)
useUser.setUserListMessages({}).then((res: any) => {
list.value = useUser.userList
})
}
onMounted(() => {
// count.value = store.count
// userList.value = store.userList
getList()
setMessage()
connectMsg()
})
//设置message
const setMessage = () => {
if (store.customerInfo.username) {
customerInfo.value = store.customerInfo
messages.value = customerInfo.value.messages || []
const isSelf = computed(() => {
return (e: any) => {
return getUseUserStore.userInfo.username == e.fromCode
}
setSrollHeight()
})
//获取子组件中state的值,这个好像是写多余了,可以直接使用automaticPromptRef.value.setState('');获取state值
const getState = (v: string) => {
msg = v
}
//获取列表的数据
const getList = async () => {
let query = {}
const getCheckMesssages = async (res: any) => {
try {
const { success, result, message }: any = await getUserList(query)
if (success) {
// let list = result.map((item: any) => {
// item.messages = []
// return item
// })
userList.value = result
store.setUserList(result)
if (customerInfo.value?.username) handleMenuClick(customerInfo.value)
let query = {
sessionId: res.sessionId,
}
const { success, result, message }: any = await checkMesssages(query)
currentInfo.value = JSON.parse(JSON.stringify(res))
console.log(currentInfo)
currentInfo.value.messages = result
messages.value = result
console.log(messages.value)
getUseUserStore.setCustomerInfo(currentInfo.value)
setSrollHeight()
} 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) {
() => getUseUserStore.userList,
(newVal, oldValue) => {
console.log(getUseUserStore.userList, '监听')
// 执行其他逻辑...
list.value = newVal
if (newVal && newVal.length > 0 && currentInfo.value.username) {
const obj =
newVal.find((item: any) => {
return item.username == cu
stomer
Info.value.username
return item.username == cu
rrent
Info.value.username
}) || {}
messages.value = obj.messages || []
getCheckMesssages(obj)
}
},
{ deep: true },
)
//监听聊天框数据的改变
watch(
messages,
(newVal, oldVal) => {
if (newVal?.length > 0) setSrollHeight()
{
deep: true,
},
{ deep: true },
)
//让聊天滑动窗口滑到底部
const setSrollHeight = () => {
nextTick(() => {
...
...
@@ -403,18 +383,7 @@ const setSrollHeight = () => {
//切换聊天人
const handleMenuClick = (item: object) => {
customerInfo.value = item
store.setCustomerInfo(item)
messages.value = item?.messages || []
const data: any = {
cmd: '19',
type: '1',
fromUserId: item?.username,
group_id: '',
userId: store.userInfo.username,
}
// sendWebSocket(data)
// getCheckMesssages(data)
getCheckMesssages(item)
}
//发送按钮
const handleButtonClick = () => {
...
...
@@ -434,56 +403,47 @@ const handleButtonClick = () => {
toLang: 'en',
chatType: '2',
group_id: '',
time: getShortDate(),
to: customerInfo.value.username,
form: store.userInfo.username,
toCode: currentInfo.value.username,
fromCode: getUseUserStore.userInfo.username,
createTime: getShortDate(),
to: currentInfo.value.username,
from: getUseUserStore.userInfo.username,
}
messages.value?.push(data)
console.log('打印客户发送的内容', data)
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 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 showContextMenu = (e: any, item: any) => {
// e.preventDefault()
...
...
@@ -514,16 +474,18 @@ const sumbitFuleForm = (formEl: FormInstance | undefined) => {
}
})
}
const handleLinkClick = (msg: string) => {
messages.value.push({ content: msg, isSent: true })
}
//
const handleLinkClick = (msg: string) => {
//
messages.value.push({ content: msg, isSent: true })
//
}
//消息框样式动态选择
const getMessageClass = (isSent: boolean) => {
return isSent ? 'message-container-right' : 'message-container-left'
const getMessageClass = (message: any) => {
return isSelf.value(message)
? 'message-container-right'
: 'message-container-left'
}
//左边按钮操作
const getMenuItem = (item: any) => {
return item.username == cu
stomer
Info.value?.username ? 'is-active' : ''
return item.username == cu
rrent
Info.value?.username ? 'is-active' : ''
}
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论