提交 3ac1e7e0 作者: Hao

add

父级 2b11e564
// 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;
},
......
......@@ -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: "/getUserList",
url: "/kf/chat/getChatList",
method: "get",
data: query,
params: query,
});
};
//查看未读消息
const checkMesssages = (query: any) => {
return http({
url: "/checkMesssages",
url: "/kf/chat/getChatHisList",
method: "get",
data: query,
params: query,
});
};
//上传接口
......@@ -62,7 +61,7 @@ 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,
};
// 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 { 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"),
console.log("调取setUserListMessages");
return new Promise((resolve, reject) => {
const query = {
code: this.userInfo.username,
};
const info = {
userId: res.id,
username: res.from,
messages: [obj],
userImg: obj.userImg,
getUserList(query).then(({ success, result, message }: any) => {
if (success) {
this.userList = result.map((item: any) => {
return {
...item,
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 {
userList.push(info);
console.log(this.userList);
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() {
// 连接成功后,将 isConnected 状态设置为 true
......
......@@ -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);
......
<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 @@
<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?.userId"
:key="item?.userId"
v-for="item in userList"
:index="item?.id"
:key="item?.id"
v-for="item in list"
@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?.time }}
{{ item?.createTime }}
</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="customerInfo.username">
<el-container class="el-container-center" v-if="currentInfo.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.time }}</div>
<div v-if="message.isSent" class="message-container">
<div class="time">{{ message.createTime }}</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 store = useUserStore()
const userList = ref([])
// const { getUseUserStore.userInfo, setcurrentInfo, userList } = useUserStore()
const getUseUserStore = useUserStore()
const list = 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 == customerInfo.value.username
return item.username == currentInfo.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 == customerInfo.value?.username ? 'is-active' : ''
return item.username == currentInfo.value?.username ? 'is-active' : ''
}
</script>
<style lang="scss" scoped>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论