提交 3ac1e7e0 作者: Hao

add

父级 2b11e564
// axios.js // axios.js
import { useUserStore } from '../store/modules/user'
import axios from "axios"; import axios from "axios";
const instance = axios.create({ const instance = axios.create({
// baseURL: "http://192.168.31.228:8080", // 设置基础 URL baseURL: "http://192.168.31.228:8080", // 设置基础 URL
baseURL:'', // baseURL:'',
timeout: 1000, // 设置请求超时时间 timeout: 1000, // 设置请求超时时间
}); });
instance.interceptors.request.use( instance.interceptors.request.use(
(config: any) => { (config: any) => {
// 在发送请求之前做些什么,例如添加 token // 在发送请求之前做些什么,例如添加 token
const token = localStorage.getItem("token"); const token = useUserStore().userInfo.token;
if (token) { if (token) {
config.headers['X-Access-Token'] = `Bearer ${token}`; config.headers['token'] = `${useUserStore().userInfo.token}`;
} }
return config; return config;
}, },
......
...@@ -6,7 +6,6 @@ const Login = (query: any) => { ...@@ -6,7 +6,6 @@ const Login = (query: any) => {
method: "post", method: "post",
data: query, data: query,
}); });
}; };
//新增询价单 //新增询价单
const InquiryAdd = (query: any) => { const InquiryAdd = (query: any) => {
...@@ -27,17 +26,17 @@ const Kjiehuifu = (query: any) => { ...@@ -27,17 +26,17 @@ const Kjiehuifu = (query: any) => {
//获取用户列表 //获取用户列表
const getUserList = (query: any) => { const getUserList = (query: any) => {
return http({ return http({
url: "/getUserList", url: "/kf/chat/getChatList",
method: "get", method: "get",
data: query, params: query,
}); });
}; };
//查看未读消息 //查看未读消息
const checkMesssages = (query: any) => { const checkMesssages = (query: any) => {
return http({ return http({
url: "/checkMesssages", url: "/kf/chat/getChatHisList",
method: "get", method: "get",
data: query, params: query,
}); });
}; };
//上传接口 //上传接口
...@@ -62,7 +61,7 @@ const deleteUserList = (query: any) => { ...@@ -62,7 +61,7 @@ const deleteUserList = (query: any) => {
return http({ return http({
url: `/deleteUserList`, url: `/deleteUserList`,
method: "delete", method: "delete",
params:query params: query,
}); });
}; };
export { export {
...@@ -73,5 +72,5 @@ export { ...@@ -73,5 +72,5 @@ export {
checkMesssages, checkMesssages,
upload, upload,
getUploadConfigInfo, getUploadConfigInfo,
deleteUserList deleteUserList,
}; };
// 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
import moment from "moment"; import moment from "moment";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { getUserList } from "@/axios/model/user";
export const useUserStore = defineStore("user", { export const useUserStore = defineStore("user", {
state: () => { state: () => {
return { return {
...@@ -9,8 +10,8 @@ export const useUserStore = defineStore("user", { ...@@ -9,8 +10,8 @@ export const useUserStore = defineStore("user", {
count: 0, count: 0,
userInfo: { userInfo: {
username: "admin", username: "admin",
password:"", password: "",
token:"" token: "",
}, },
userList: [], userList: [],
}; };
...@@ -25,57 +26,29 @@ export const useUserStore = defineStore("user", { ...@@ -25,57 +26,29 @@ export const useUserStore = defineStore("user", {
setUserInfo(res: any) { setUserInfo(res: any) {
this.userInfo = res; this.userInfo = res;
}, },
setUserList(res: any) { // setUserList(res: any) {
this.userList = res; // this.userList = res;
}, // },
setUserListMessages(res: any) { setUserListMessages(res: any) {
console.log(res) console.log("调取setUserListMessages");
const userList: any = this.userList || []; return new Promise((resolve, reject) => {
// res.from = res.from.replace('游客','') const query = {
const obj = { code: this.userInfo.username,
...res,
isSent: false,
userImg:
"https://cdn.lirimall.com//lirigo/filetempImage/新鲜水果_1661668973048.png",
time: moment(res.createTime).format("YYYY-DD-MM HH:mm:ss"),
}; };
getUserList(query).then(({ success, result, message }: any) => {
const info = { if (success) {
userId: res.id, this.userList = result.map((item: any) => {
username: res.from, return {
messages: [obj], ...item,
userImg: obj.userImg, username: item.id,
messages: [],
}; };
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 { console.log(this.userList);
userList.push(info); resolve(result);
} }
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");
}); });
for (const index in chatDatas) {
const userId = chatDatas[index].from;
this.userList.forEach((item: any) => {
if (item && item?.username == userId) {
item.messages = chatDatas;
}
}); });
}
}
}, },
connect() { connect() {
// 连接成功后,将 isConnected 状态设置为 true // 连接成功后,将 isConnected 状态设置为 true
......
...@@ -26,7 +26,7 @@ function creatWebSocket(wsUrl: string) { ...@@ -26,7 +26,7 @@ function creatWebSocket(wsUrl: string) {
// 初始化websocket // 初始化websocket
function initWebSocket(wsUrl: string) { function initWebSocket(wsUrl: string) {
if (!websocket) websocket = new WebSocket(wsUrl); if (!websocket) websocket = new WebSocket(wsUrl);
websocket.onopen = function () { websocket.onopen = function (eee) {
websocketOpen(); websocketOpen();
}; };
// // 接收 // // 接收
...@@ -64,14 +64,14 @@ function websocketOpen() { ...@@ -64,14 +64,14 @@ function websocketOpen() {
} }
// 数据接收 // 数据接收
function websocketonmessage(e: MessageEvent<any>) { function websocketonmessage(e: MessageEvent<any>) {
console.log('接受数据',e.data)
const res = JSON.parse(e.data); // 解析JSON格式的数据 const res = JSON.parse(e.data); // 解析JSON格式的数据
// 下面的判断则是后台返回的接收到的数据 如何处理自己决定 console.log(res,'res')
if (res.command == 11) { if (res.command == 11) {
//将数据放在store中 //将数据放在store中
useUserStore().setUserListMessages(res.data); // 下面的判断则是后台返回的接收到的数据 如何处理自己决定
} else if (res.command == 20) { useUserStore()
useUserStore().setusernameMessage(res.data); .setUserListMessages(res.data)
.then();
} }
} }
...@@ -82,7 +82,6 @@ function websocketclose(e: any) { ...@@ -82,7 +82,6 @@ function websocketclose(e: any) {
// 数据发送 // 数据发送
function websocketsend(res: any) { function websocketsend(res: any) {
console.log(websocket, "websocket");
if (websocket && useUserStore().isConnected) { if (websocket && useUserStore().isConnected) {
// 检查连接状态 // 检查连接状态
console.log("发送的数据", websocket); console.log("发送的数据", websocket);
......
<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>
...@@ -5,17 +5,13 @@ ...@@ -5,17 +5,13 @@
<el-header class="el-header-left"> <el-header class="el-header-left">
<el-input placeholder="搜索" /> <el-input placeholder="搜索" />
</el-header> </el-header>
<div <div active-text-color="#000" background-color="#fff">
active-text-color="#000"
background-color="#fff"
default-active="1"
>
<div <div
class="el-menu-item" class="el-menu-item"
:class="getMenuItem(item)" :class="getMenuItem(item)"
:index="item?.userId" :index="item?.id"
:key="item?.userId" :key="item?.id"
v-for="item in userList" v-for="item in list"
@click="handleMenuClick(item)" @click="handleMenuClick(item)"
> >
<div class="user-info-box"> <div class="user-info-box">
...@@ -25,20 +21,14 @@ ...@@ -25,20 +21,14 @@
<div class="user-info-right"> <div class="user-info-right">
<div class="user-box-right-nbs"> <div class="user-box-right-nbs">
<div class="user-name-box"> <div class="user-name-box">
<span class="user-name">{{ item?.username }}</span> <span class="user-name">{{ item?.nick }}</span>
<div class="user-time"> <div class="user-time">
{{ item?.time }} {{ item?.createTime }}
</div> </div>
</div> </div>
<div class="user-reply-box"> <div class="user-reply-box">
<span class="user-reply"> <span class="user-reply">
{{ {{ item.content }}
item?.messages[
item?.messages.length - 1
]?.content.includes('img')
? '[图文]'
: item.messages[item.messages.length - 1]?.content
}}
</span> </span>
<div class="count">1</div> <div class="count">1</div>
</div> </div>
...@@ -48,10 +38,12 @@ ...@@ -48,10 +38,12 @@
</div> </div>
</div> </div>
</el-aside> </el-aside>
<el-container class="el-container-center" v-if="customerInfo.username"> <el-container class="el-container-center" v-if="currentInfo.username">
<el-header class="el-header-center"> <el-header class="el-header-center">
<p class="name">{{ customerInfo.username }}</p> <p class="name">{{ currentInfo.nick }}</p>
<p class="url">US|https://www.ibeautytop.com</p> <p class="url">
{{ currentInfo.sessionId }}{{ currentInfo.orginUrl }}
</p>
</el-header> </el-header>
<el-main <el-main
id="srollId" id="srollId"
...@@ -66,10 +58,10 @@ ...@@ -66,10 +58,10 @@
class="message-container" class="message-container"
v-for="(message, index) in messages" v-for="(message, index) in messages"
:key="index" :key="index"
:class="getMessageClass(message?.isSent)" :class="getMessageClass(message)"
> >
<div class="time">{{ message.time }}</div> <div class="time">{{ message.createTime }}</div>
<div v-if="message.isSent" class="message-container"> <div v-if="isSelf(message)" class="message-container">
<div class="bubble" @contextmenu="showContextMenu1($event, item)"> <div class="bubble" @contextmenu="showContextMenu1($event, item)">
<div v-if="message?.msgType == 0" class="message"> <div v-if="message?.msgType == 0" class="message">
<div class="content" v-html="message?.content"></div> <div class="content" v-html="message?.content"></div>
...@@ -90,7 +82,7 @@ ...@@ -90,7 +82,7 @@
/> />
</div> </div>
</div> </div>
<div v-if="!message.isSent" class="message-container"> <div v-if="!isSelf(message)" class="message-container">
<div class="avatar"> <div class="avatar">
<img :src="message?.userImg" class="avatar-image" /> <img :src="message?.userImg" class="avatar-image" />
</div> </div>
...@@ -243,7 +235,7 @@ ...@@ -243,7 +235,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import '../assets/font/iconfont.css' import '../assets/font/iconfont.css'
import { ElMessage } from 'element-plus' 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 { getShortDate } from '../utils/index'
import { useUserStore } from '../store/modules/user' import { useUserStore } from '../store/modules/user'
import AutomaticPrompt from '@/components/AutomaticPrompt.vue' import AutomaticPrompt from '@/components/AutomaticPrompt.vue'
...@@ -254,31 +246,18 @@ import { initWebSocket } from '../utils/websocket' ...@@ -254,31 +246,18 @@ import { initWebSocket } from '../utils/websocket'
import '@luohc92/vue3-image-viewer/dist/style.css' import '@luohc92/vue3-image-viewer/dist/style.css'
import { getUserList, InquiryAdd, checkMesssages } from '../axios/model/user' import { getUserList, InquiryAdd, checkMesssages } from '../axios/model/user'
import useMenu from '@/components/Menu/RightClickMenu' import useMenu from '@/components/Menu/RightClickMenu'
import RightClickMenu from '@/components/RightClickMenu.vue' let { showContextMenu1, hideContextMenu2 } = useMenu()
let {showContextMenu1,hideContextMenu2} = useMenu()
// let { showContextMenu1, hideContextMenu2 } = openRightClickMenu()
//显示右键菜单的函数,在右键点击时调用。 //显示右键菜单的函数,在右键点击时调用。
//它阻止了默认的右键菜单并设置了菜单的位置,同时添加了一个点击事件监听器来在其他地方点击时隐藏菜单。 //它阻止了默认的右键菜单并设置了菜单的位置,同时添加了一个点击事件监听器来在其他地方点击时隐藏菜单。
//处理右键菜单项点击的函数,你可以在这里处理点击菜单项后的逻辑。 //处理右键菜单项点击的函数,你可以在这里处理点击菜单项后的逻辑。
const ruleForm = ref({ const ruleForm = ref({
count: 1, 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 ruleFormRef = ref<FormInstance>()
const customerInfo = ref({})
const messages = ref([]) const messages = ref([])
const count = ref(0) // const { getUseUserStore.userInfo, setcurrentInfo, userList } = useUserStore()
const store = useUserStore() const getUseUserStore = useUserStore()
const userList = ref([]) const list = ref([])
const automaticPromptRef = ref('') const automaticPromptRef = ref('')
const inquiryInfo = ref({ const inquiryInfo = ref({
price: '20.00', price: '20.00',
...@@ -331,68 +310,69 @@ const rules = reactive({ ...@@ -331,68 +310,69 @@ const rules = reactive({
}) })
let msg = '' 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(() => { onMounted(() => {
// count.value = store.count connectMsg()
// userList.value = store.userList
getList()
setMessage()
}) })
//设置message //设置message
const setMessage = () => { const isSelf = computed(() => {
if (store.customerInfo.username) { return (e: any) => {
customerInfo.value = store.customerInfo return getUseUserStore.userInfo.username == e.fromCode
messages.value = customerInfo.value.messages || []
} }
setSrollHeight() })
//获取子组件中state的值,这个好像是写多余了,可以直接使用automaticPromptRef.value.setState('');获取state值
const getState = (v: string) => {
msg = v
} }
//获取列表的数据 const getCheckMesssages = async (res: any) => {
const getList = async () => {
let query = {}
try { try {
const { success, result, message }: any = await getUserList(query) let query = {
if (success) { sessionId: res.sessionId,
// let list = result.map((item: any) => { }
// item.messages = [] const { success, result, message }: any = await checkMesssages(query)
// return item currentInfo.value = JSON.parse(JSON.stringify(res))
// }) console.log(currentInfo)
userList.value = result currentInfo.value.messages = result
store.setUserList(result) messages.value = result
if (customerInfo.value?.username) handleMenuClick(customerInfo.value) console.log(messages.value)
} getUseUserStore.setCustomerInfo(currentInfo.value)
setSrollHeight()
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
} }
//获取子组件中state的值,这个好像是写多余了,可以直接使用automaticPromptRef.value.setState('');获取state值
const getState = (v: string) => {
msg = v
}
const handleChange = (val: any) => {
store.setCount(val)
}
//监听聊天数据的改变 //监听聊天数据的改变
watch( watch(
userList, () => getUseUserStore.userList,
(newVal, oldVal) => { (newVal, oldValue) => {
if (newVal && newVal.length > 0 && customerInfo.value.username) { console.log(getUseUserStore.userList, '监听')
// 执行其他逻辑...
list.value = newVal
if (newVal && newVal.length > 0 && currentInfo.value.username) {
const obj = const obj =
newVal.find((item: any) => { newVal.find((item: any) => {
return item.username == customerInfo.value.username return item.username == currentInfo.value.username
}) || {} }) || {}
messages.value = obj.messages || [] getCheckMesssages(obj)
} }
}, },
{ deep: true }, {
) deep: true,
//监听聊天框数据的改变
watch(
messages,
(newVal, oldVal) => {
if (newVal?.length > 0) setSrollHeight()
}, },
{ deep: true },
) )
//让聊天滑动窗口滑到底部 //让聊天滑动窗口滑到底部
const setSrollHeight = () => { const setSrollHeight = () => {
nextTick(() => { nextTick(() => {
...@@ -403,18 +383,7 @@ const setSrollHeight = () => { ...@@ -403,18 +383,7 @@ const setSrollHeight = () => {
//切换聊天人 //切换聊天人
const handleMenuClick = (item: object) => { const handleMenuClick = (item: object) => {
customerInfo.value = item getCheckMesssages(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 = () => { const handleButtonClick = () => {
...@@ -434,56 +403,47 @@ const handleButtonClick = () => { ...@@ -434,56 +403,47 @@ const handleButtonClick = () => {
toLang: 'en', toLang: 'en',
chatType: '2', chatType: '2',
group_id: '', group_id: '',
time: getShortDate(), toCode: currentInfo.value.username,
to: customerInfo.value.username, fromCode: getUseUserStore.userInfo.username,
form: store.userInfo.username, createTime: getShortDate(),
to: currentInfo.value.username,
from: getUseUserStore.userInfo.username,
} }
messages.value?.push(data) messages.value.push(data)
console.log('打印客户发送的内容', data)
sendWebSocket(data) sendWebSocket(data)
automaticPromptRef.value.setState('') automaticPromptRef.value.setState('')
} }
const handleMessageClick = (event: any) => { // const handleMessageClick = (event: any) => {
const target = event.target // const target = event.target
if (target.tagName === 'A') { // if (target.tagName === 'A') {
// 点击的是超链接 // // 点击的是超链接
// 执行相应的操作 // // 执行相应的操作
if (target.innerHTML === '解决') { // if (target.innerHTML === '解决') {
alert('感谢您的使用') // alert('感谢您的使用')
} else if (target.innerHTML === '未解决') { // } else if (target.innerHTML === '未解决') {
alert('很抱歉未能解决你的问题') // alert('很抱歉未能解决你的问题')
} else { // } else {
handleLinkClick(target.innerHTML) // handleLinkClick(target.innerHTML)
} // }
} else if (target.tagName === 'IMG') { // } else if (target.tagName === 'IMG') {
// 点击的图片进行放大操作 // // 点击的图片进行放大操作
ImageViewer({ // ImageViewer({
//切记额images这个参数是数组,我的target.valueof().src值是一个http的图片地址 // //切记额images这个参数是数组,我的target.valueof().src值是一个http的图片地址
images: [target.valueOf().src], // images: [target.valueOf().src],
curIndex: 0, // curIndex: 0,
zIndex: 2000, // zIndex: 2000,
showDownload: true, // showDownload: true,
showThumbnail: true, // showThumbnail: true,
handlePosition: 'bottom', // handlePosition: 'bottom',
maskBgColor: 'rgba(0,0,0,0.7)', // maskBgColor: 'rgba(0,0,0,0.7)',
onClose: () => { // onClose: () => {
console.log('close') // 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) => { // const showContextMenu = (e: any, item: any) => {
// e.preventDefault() // e.preventDefault()
...@@ -514,16 +474,18 @@ const sumbitFuleForm = (formEl: FormInstance | undefined) => { ...@@ -514,16 +474,18 @@ const sumbitFuleForm = (formEl: FormInstance | undefined) => {
} }
}) })
} }
const handleLinkClick = (msg: string) => { // const handleLinkClick = (msg: string) => {
messages.value.push({ content: msg, isSent: true }) // messages.value.push({ content: msg, isSent: true })
} // }
//消息框样式动态选择 //消息框样式动态选择
const getMessageClass = (isSent: boolean) => { const getMessageClass = (message: any) => {
return isSent ? 'message-container-right' : 'message-container-left' return isSelf.value(message)
? 'message-container-right'
: 'message-container-left'
} }
//左边按钮操作 //左边按钮操作
const getMenuItem = (item: any) => { const getMenuItem = (item: any) => {
return item.username == customerInfo.value?.username ? 'is-active' : '' return item.username == currentInfo.value?.username ? 'is-active' : ''
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论