提交 2b11e564 作者: Hao

add

父级 2acb94ab
......@@ -21,6 +21,7 @@ declare module 'vue' {
ElMain: typeof import('element-plus/es')['ElMain']
EmotionComponent: typeof import('./src/components/EmotionComponent.vue')['default']
IndexComponent: typeof import('./src/components/IndexComponent.vue')['default']
LogingComponent: typeof import('./src/components/loging/logingComponent.vue')['default']
MenuComponent: typeof import('./src/components/Menu/MenuComponent.vue')['default']
RightClickMenu: typeof import('./src/components/RightClickMenu.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
......
import { h, render, reactive, ref, defineEmits } from "vue";
import RightClickMenu from "./RightClickMenu.vue";
// export function openRightClickMenu(e: MouseEvent) {
// const contextMenuVisible = ref(false);
// const contextMenuTop = ref(0);
// const contextMenuLeft = ref(0);
// const contextMenuIndex = ref(0);
// const scope = document.body;
// const containerEl: any = document.createElement("div");
// const vnode:any = h(RightClickMenu, {
// visible: contextMenuVisible.value,
// menuList: [
// "复制",
// "翻译",
// "设为客户名称",
// "设置国家/地区",
// "设为Email",
// "设为watchApp",
// ],
// active: contextMenuIndex.value,
// handleItemClick: (item: any, index: number) => {
// console.log(item);
// contextMenuIndex.value = index;
// },
// });
// render(vnode, containerEl);
// containerEl.style.position = "fixed";
// containerEl.style["z-index"] = "1000";
// scope?.appendChild(containerEl);
// const showContextMenu1 = (event: any, item: any) => {
// //阻止默认右键事件
// 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;
// }
// containerEl.style.top = `${contextMenuTop.value}px`;
// containerEl.style.left = `${contextMenuLeft.value}px`;
// window.addEventListener("click", hideContextMenu2);
// };
// //隐藏右键菜单的函数,移除点击事件监听器,并将 contextMenuVisible 设置为 false
// const hideContextMenu2 = (e: any) => {
// console.log(vnode,'打印');
// const pop: any = document.querySelector("#select-box1");
// if (!e || (e?.target && !pop.contains(e?.target))) {
// contextMenuVisible.value = false;
// vnode.props.visible = false;
// console.log(contextMenuVisible.value, "关闭");
// window.removeEventListener("click", hideContextMenu2);
// }
// };
// return {
// showContextMenu1,
// hideContextMenu2,
// };
// }
export default function useMenu(e: MouseEvent) {
const contextMenuVisible = ref(true);
const contextMenuTop = ref(0);
const contextMenuLeft = ref(0);
const contextMenuIndex = ref(0);
const scope = document.body;
let containerEl = reactive<HTMLElement | any>(null);
let vnode = reactive<any>(null);
let props = reactive<any>({});
const setComponent = () => {
props = {
visible: contextMenuVisible.value,
menuList: [
"复制",
"翻译",
"设为客户名称",
"设置国家/地区",
"设为Email",
"设为watchApp",
],
active: contextMenuIndex.value,
"onUpdate:info": (info: any) => {
contextMenuIndex.value = info.index;
setComponent();
},
};
vnode = h(RightClickMenu, props);
render(vnode, scope);
};
const openRightClickMenu = (e: MouseEvent) => {
setComponent();
containerEl = document.getElementById("select-box1");
if (containerEl) containerEl.style.position = "fixed";
if (containerEl) containerEl.style["z-index"] = "1000";
};
const showContextMenu1 = (event: any, item: any) => {
//阻止默认右键事件
event.preventDefault();
//菜单可见
contextMenuVisible.value = true;
openRightClickMenu(e)
//获取鼠标坐标
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;
}
containerEl.style.top = `${contextMenuTop.value}px`;
containerEl.style.left = `${contextMenuLeft.value}px`;
window.addEventListener("click", hideContextMenu2);
};
//隐藏右键菜单的函数,移除点击事件监听器,并将 contextMenuVisible 设置为 false
const hideContextMenu2 = (e: any) => {
const pop: any = document.querySelector("#select-box1");
if (!e || (e?.target && !pop.contains(e?.target))) {
contextMenuVisible.value = false;
setComponent();
window.removeEventListener("click", hideContextMenu2);
}
};
return {
contextMenuVisible,
openRightClickMenu,
showContextMenu1,
hideContextMenu2,
};
}
<template>
<teleport to="body">
<div
id="select-box1"
v-if="visible"
:style="{ top: `${top}px`, left: `${left}px` }"
>
<ul
v-for="(item, index) in menuList"
:class="{ active: active == index }"
:key="index"
@click.stop="handleItemClick(item, index)"
>
<li>{{ item }}</li>
</ul>
</div>
</teleport>
</template>
<script setup lang="ts">
import { defineProps, onMounted, defineEmits } from 'vue'
defineProps(['top', 'left', 'visible', 'menuList', 'active'])
const emit = defineEmits(['update:info'])
const handleItemClick = (item: any, index: any) => {
emit('update:info', { item, index })
}
</script>
<style scoped lang="scss">
#select-box1 {
z-index: 1000;
display: flex;
width: 136px;
padding: var(--Spacing-md, 8px);
flex-direction: column;
align-items: flex-start;
gap: var(--Spacing-xs, 4px);
border-radius: var(--Radius-md, 8px);
background: var(--color-bg-Program-White, #fff);
box-shadow: 0 2px 12px 0 #08112914;
ul {
padding: 6px 8px;
height: 32px;
line-height: 32px;
}
.active {
display: flex;
padding: var(--Spacing-sm, 6px) var(--Spacing-md, 8px);
align-items: center;
gap: var(--Spacing-none, 0);
align-self: stretch;
color: #fff;
border-radius: 4px;
background: #010914;
font-size: 14px;
}
}
</style>
......@@ -28,11 +28,11 @@ export function openCountextMenus(e: Event) {
isShow = false;
}
}
// window.oncontextmenu = function (e: MouseEvent) {
// e.preventDefault();
// if (isShow) closeMenu();
// openMenu(e);
// };
// window.oncontextmenu = function (e: MouseEvent) {
// e.preventDefault();
// if (isShow) closeMenu();
// openMenu(e);
// };
return {
isShow,
openMenu,
......
<template>
<div class="loading">
<div></div>
<span v-if="$attrs.msg !== false">{{ $attrs.msg }}</span>
</div>
</template>
<script lang="ts">
export default {
}
</script>
<style>
@keyframes identifier {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.loading {
height: 100px;
width: 100%;
}
.loading div {
width: 50px;
height: 50px;
border-radius: 50%;
border: 2px solid green;
margin: 25px auto;
border-top: none;
border-left: none;
animation: identifier 1s infinite linear;
}
</style>
\ No newline at end of file
......@@ -12,6 +12,12 @@ const routes: Array<RouteRecordRaw> = [
component: () =>
import(/* webpackChunkName: "about" */ "../views/loginView.vue"),
},
{
path: "/loging",
name: "loging",
component: () =>
import(/* webpackChunkName: "about" */ "../views/logingView.vue"),
},
];
const router = createRouter({
......
<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>
......@@ -70,7 +70,7 @@
>
<div class="time">{{ message.time }}</div>
<div v-if="message.isSent" class="message-container">
<div class="bubble" @contextmenu="showContextMenu($event, item)">
<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>
......@@ -94,7 +94,7 @@
<div class="avatar">
<img :src="message?.userImg" class="avatar-image" />
</div>
<div class="bubble" @contextmenu="showContextMenu($event, item)">
<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>
......@@ -108,15 +108,6 @@
</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
......@@ -262,57 +253,13 @@ 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 { openCountextMenus } from '@/components/Menu/index'
import useMenu from '@/components/Menu/RightClickMenu'
import RightClickMenu from '@/components/RightClickMenu.vue'
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)
let {showContextMenu1,hideContextMenu2} = useMenu()
// let { showContextMenu1, hideContextMenu2 } = openRightClickMenu()
//显示右键菜单的函数,在右键点击时调用。
//它阻止了默认的右键菜单并设置了菜单的位置,同时添加了一个点击事件监听器来在其他地方点击时隐藏菜单。
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,
})
......
<script setup lang="ts">
import { Directive, VNode, h, ref, render } from 'vue';
import Loading from '@/components/loging/logingComponent.vue';
const defaultOption: {msg?: string | false} = {
msg: '努力加载中'
}
interface Props {loading: boolean, msg?: string | false}
function formatOption (value: boolean | Props) {
const loading = typeof value === 'boolean'
? value
: value.loading
const option = typeof value !== 'boolean'
? Object.assign(defaultOption, value)
: {
loading,
...defaultOption
}
return { loading, option }
}
function renderLoading (el: HTMLElement, option: Props) {
const vnode = h(
Loading,
{ id: 'loading', ...option}
)
el.removeChild(el.children[0])
render(vnode, el)
}
function renderVNode (el: HTMLElement, vnode: VNode) {
el.querySelector('#loading')?.remove()
render(vnode, el)
}
const vLoading: Directive<HTMLElement, boolean | Props> = {
mounted(el, binding) {
const { loading, option } = formatOption(binding.value)
loading && renderLoading(el, option)
},
updated(el: HTMLElement, binding, vnode) {
const { loading, option } = formatOption(binding.value)
if (loading) {
renderLoading(el, option)
} else {
renderVNode(el, vnode)
}
},
}
const loading = ref(true)
setTimeout(() => loading.value = false, 2000)
</script>
<template>
<div style="width: 300px" v-loading="{ loading, msg: '一大波僵尸来袭' }">
<h1>加载成功</h1>
</div>
</template>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论