branchErp/src/views/atrist-app/customerservice/index.vue
2025-10-24 09:02:17 +08:00

1988 lines
55 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="customer-service-page row" style="padding: 35px">
<fln-table
:config="state.tableConfig"
tableType=""
:hideDataTable="state.hideDataTable"
@customerServiceSearch="searchCustomer"
>
<template #search-header>
<div
class="col-12 row font-18"
style="
color: #764cf6;
border-bottom: 1px solid #c1b2e5;
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div
class="fl-py-sm font-bold"
style="border-bottom: 4px solid #764cf6"
>
客服
</div>
<div
style="
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: #46299d;
cursor: pointer;
"
@click="showCreateDialog"
>
<span>创建会话</span>
</div>
</div>
</template>
<template #table-header-box>
<div
class="customer-service"
:style="'height:' + state.serviceAreaHeight + 'px'"
>
<div class="customer-list-area">
<div
class="customer-list"
ref="scrollCustomerList"
:style="
state.customerList.length == 0 ? 'padding: 13px 21px;' : ''
"
>
<div
class="customer-list-each"
v-for="(item, index) in state.customerList"
:key="index"
@click="changeCustomer(item, index)"
v-if="state.customerList.length > 0"
>
<div
class="customer-list-each-leftLine"
:style="
item.isActive
? 'background-color: #46299D;'
: 'background-color: #C2C2C2;'
"
></div>
<span class="font-14">{{ item.name }}</span>
<div
class="customer-list-each-unread-msg-count"
:style="item.total ? '' : 'background-color:#fff;'"
>
<span>{{ item.total }}</span>
</div>
<img
src="@/assets/image/icon/close-circle-grey.png"
@click.stop="closeChatCustomer(index)"
/>
</div>
<div class="chat-no-data" v-if="state.customerList.length == 0">
<img src="@/assets/image/customerService/chat-nodata.png" />
<span>暂时没有消息哦~</span>
</div>
</div>
<div
v-if="state.customerList.length > 0"
class="customer-list-back-to-top-btn"
@click="scrollCustomerListToTop"
>
<img src="@/assets/image/customerService/back-to-top.png" />
<span>顶部</span>
</div>
</div>
<div
class="chat-service"
:style="'height:' + state.serviceAreaHeight + 'px'"
v-if="state.customerList.length > 0"
>
<div class="chat-service-userInfo">
<div class="chat-service-userInfo-detail">
<div
class="chat-service-userInfo-detail-each"
:style="index > 2 ? 'margin: 28px 0 0;' : ''"
v-for="(item, index) in state.customerInfoList"
:key="index"
>
<span class="font-14">{{ item.label }}</span>
<span class="font-14">{{
item.value ? item.value : "--"
}}</span>
</div>
</div>
<div class="chat-service-userInfo-photo">
<span class="font-14">近照:</span>
<a-image
style="
width: 119px;
height: 59px;
flex-shrink: 0;
object-fit: cover;
"
:src="state.artistInfo.recentPhoto"
/>
</div>
</div>
<div
class="chat-list"
ref="scrollChatList"
:style="'height:' + state.chatListHeight + 'px'"
>
<div
class="chat-list-getHistoryRecord-btn"
@click="getHistoryRecord"
>
<img
class="getHistoryRecord-loading"
src="@/assets/image/customerService/chat-loading.png"
v-if="state.loadPrevChat"
/>
<img
class="getHistoryRecord-arrow"
src="@/assets/image/customerService/arrow-up-purple.png"
v-if="!state.loadPrevChat && !state.hasNomoreChat"
/>
<span class="font-14">{{
state.loadPrevChat
? "正在加载中..."
: state.hasNomoreChat
? "没有更多啦~"
: "点击此处唤起聊天记录"
}}</span>
</div>
<div
class="chat-list-each"
v-for="(item, index) in state.chatList"
:key="index"
:id="'chatEach' + item.ID"
:class="
item.eventType == 'send' ? 'chat-list-each-rightAlign' : ''
"
>
<div
class="chat-tool-tip"
v-if="item.showToolTip"
@touchstart.stop
@click.stop
:style="
item.eventType == 'send' ? 'right: 54px;' : 'left: 54px;'
"
>
<div
class="chat-tool-tip-each"
@click="handleToolTipClick(item, $event)"
>
<n-button
color="#505050"
:focusable="false"
text-color="#fff"
>转文字</n-button
>
</div>
</div>
<div
@click="handleCustomerHeadImgClick(item)"
class="chat-each-headImg"
style="cursor: pointer"
v-if="item.eventType == 'receive'"
>
<span
class="font-14 font-bolder"
:style="item.name.length > 3 ? 'width:30px;' : ''"
>{{ item.name }}</span
>
</div>
<div class="chat-each-content">
<div
class="chat-each-details"
@click="handleChatEachClick(item)"
@mousedown="handleMouseDownChatItem($event, item)"
@mouseup="handleMouseUpChatItem($event)"
@mouseout="handleMouseOutChatItem($event)"
:class="
item?.message?.msgType == 3
? item.eventType == 'receive'
? 'chat-each-details-leftAlign'
: 'chat-each-details-rightAlign'
: ''
"
>
<span
class="font-14 font-regular"
v-if="item?.message?.msgType == 1"
>{{
item.message && item.message.text
? item.message.text
: ""
}}</span
>
<a-image
style="width: 216px; height: 125px; object-fit: cover"
v-if="item?.message?.msgType == 2"
:src="
item.message &&
item.message.media &&
item.message.media.length > 0 &&
item.message.media[0].url
? item.message.media[0].url
: ''
"
/>
<aTrumpet
:isPlay="item.isPlay"
v-if="
item.message.msgType == 3 && item.eventType == 'receive'
"
color="#fff"
:size="30"
></aTrumpet>
<span
class="font-14 font-regular"
v-if="item?.message?.msgType == 3"
>{{
item.message &&
item.message.media &&
item.message.media.length > 0 &&
item.message.media[0].duration
? item.message.media[0].durationText
: 0
}}</span
>
<aTrumpet
:isPlay="item.isPlay"
v-if="
item?.message?.msgType == 3 && item.eventType == 'send'
"
color="#C1C1C1"
:size="30"
direction="left"
></aTrumpet>
<audio
v-if="item?.message?.msgType == 3"
:id="'voiceAudio' + item.ID"
>
<source :src="state.voiceSrc" type="audio/mpeg" />
</audio>
</div>
<div
class="chat-each-details"
v-if="item?.message?.msgType == 3 && item.voiceToText"
>
<span class="font-14 font-regular">{{
item.voiceToText
}}</span>
</div>
<div class="chat-each-dateTime">
<span class="font-14 font-regular">{{
item.createdAt
}}</span>
</div>
</div>
<div
class="chat-each-headImg"
v-if="item.eventType == 'send'"
style="background-color: #fff"
>
<!-- <span class="font-14 font-bolder">{{ item.name }}</span> -->
<img
class=""
src="@/assets/image/customerService/customerServiceStaffHeadImg.png"
/>
</div>
</div>
</div>
<div class="chat-input-area">
<Upload
slotType="iconOnly"
@UploadChange="(e) => uploadImageChange(e)"
uploadUrl="api/aschat/media/upload"
>
</Upload>
<!-- <img src="@/assets/image/icon/upload-img-grey.png" /> -->
<n-input
placeholder="请输入文字"
v-model:value="state.megSendData.sendMessageText"
@focus="() => inputMsgOnFocus()"
@blur="() => inputMsgOnBlur()"
></n-input>
<n-button
color="#46299D"
class="chat-input-area-send-btn"
@click="sendMessage(1)"
:loading="state.sendLoading"
>发送</n-button
>
</div>
</div>
<div class="chat-no-data" v-if="state.customerList.length == 0">
<img src="@/assets/image/customerService/chat-nodata.png" />
<span>暂时没有消息哦~</span>
</div>
</div>
</template>
</fln-table>
</div>
<n-modal
v-model:show="state.dialogInitiate"
style="width: 1200px"
:mask-closable="false"
preset="card"
>
<template #header>
<div
class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2"
>
<div
style="
font-size: 20px;
font-weight: bold;
color: #1f2225ff;
margin: 0 0 15px;
"
>
资料查看
</div>
</div>
</template>
<div class="customer-service-modal-body">
<n-button
color="#46299D"
class="modal-body-btns view-artist-btn"
@click="viewArtistInfo"
>查看画家信息</n-button
>
<n-button
color="#EEE9F8"
class="modal-body-btns view-artwork-btn"
text-color="#46299D"
@click="viewArtworkInfo"
>查看画作信息</n-button
>
</div>
</n-modal>
<n-modal
v-model:show="state.isShowCreateDialog"
style="width: 1385px"
:mask-closable="false"
preset="card"
:z-index="999"
>
<template #header>
<div
class="col-12 row justify-center relative"
style="border-bottom: 1px solid #e9e9e9"
>
<div
style="
font-size: 20px;
font-weight: bold;
color: #1f2225;
margin: 0 0 15px;
"
>
创建会话
</div>
</div>
</template>
<div class="artist-search-area">
<fln-search
:config="state.artistSearchConfig"
:btnLoading="state.artistSearchBtnLoading"
@triggerSearchChange="handleArtistSearchChange"
></fln-search>
<div style="margin: 26px 0 0">
<span>画家选择</span>
</div>
<div
class="artist-list"
v-if="
state.artistList.length > 0 &&
(state.searchObj.ArtistName || state.searchObj.Tnum)
"
>
<div class="artist-list-loading" v-if="state.artistSearchBtnLoading">
<img
class="artist-list-loading-image"
src="@/assets/image/customerService/chat-loading.png"
/>
</div>
<div
class="artist-list-each"
v-for="(artistItem, artistIndex) in state.artistList"
:key="artistIndex"
>
<div class="artist-info-detail">
<div
class="artist-info-detail-each"
:style="index > 2 ? 'margin: 28px 0 0;' : ''"
v-for="(item, index) in artistItem.artistInfoList"
:key="index"
>
<span class="font-14">{{ item.label }}</span>
<span class="font-14">{{ item.value ? item.value : "--" }}</span>
</div>
</div>
<div class="artist-info-photo">
<span class="font-14">近照:</span>
<a-image
v-if="artistItem.photo"
width="119px"
height="59px"
:src="artistItem.photo"
/>
<span v-if="!artistItem.photo" style="width: 119px; margin: 0"
>--</span
>
</div>
<div
class="artist-start-dialog-btn"
@click="createDialog(artistItem)"
>
<span>开始会话</span>
</div>
</div>
</div>
<div class="artist-list-pagination">
<n-pagination
v-if="
state.artistList.length > 0 &&
(state.searchObj.ArtistName || state.searchObj.Tnum)
"
v-model:page="state.pagination.current"
v-model:page-size="state.pagination.pageSize"
:show-size-picker="state.pagination.showSizePicker"
:show-quick-jumper="state.pagination.showQuickJumper"
:item-count="state.pagination.total"
:page-sizes="state.pagination.pageSizeOptions"
:on-update:page="handlePagination"
:on-update:page-size="handlePaginationSize"
/>
</div>
<div
class="no-artist"
v-if="
state.artistList.length === 0 ||
(!state.searchObj.ArtistName && !state.searchObj.Tnum)
"
>
<img src="@/assets/image/customerService/no-artist.png" />
<text>无相关匹配画家</text>
</div>
</div>
</n-modal>
<!-- <Tabs
v-model:showModal="state.showModal"
:detailInfo="detailInfo"
@closeFn="closeFn"
/> -->
<!-- <a-modal
v-if="state.artworkModalVisible"
v-model:visible="state.artworkModalVisible"
width="1400px"
class="fl-modal-artwork"
:bodyStyle="{ height: '90vh' }"
style="top: 60px"
:closable="false"
:maskClosable="false"
:keyboard="false"
:footer="null"
:getContainer="() => $refs.modalView"
>
<fl-artwork-modal
:pageType="state.pageType"
:fatherData="state.rowData"
@triggerCloseModal="handleCloseModal"
></fl-artwork-modal>
</a-modal> -->
<div ref="modalView"></div>
</template>
<script setup>
import flnTable from "@/components/flnlayout/table/flntable.vue";
import aTrumpet from "./components/a-trumpet/a-trumpet.vue";
// import Tabs from "../../artist/components/tabs.vue";
// import flArtworkModal from "../../artwork/artworkmodal.vue";
import {
reactive,
onMounted,
onBeforeUnmount,
ref,
getCurrentInstance,
} from "vue";
import useWebSocket from "@/utils/webSocket";
import { Local } from "@/utils/storage";
import customerServiceApi from "@/api-norm/customerService/index.js";
import Upload from "@/components/upload";
import utils from "@/utils/commonUtils";
import flnSearch from "@/components/flnlayout/search/flnsearch.vue";
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const state = reactive({
btnLoading: false,
listUrl: {
resDataField: "data",
url: "",
params: [],
},
tableConfig: {
searchConfig: [
{
type: "text",
label: "画家姓名",
field: "telNum",
},
],
columns: [],
},
customerList: [], //客户列表
hideDataTable: true, //是否隐藏表格
chatList: [], //聊天列表
windowHeight: 0, //窗口高度
serviceAreaHeight: 0, //客服区域高度
chatListHeight: 0, //聊天区域高度
dialogInitiate: false, //是否显示模态弹框
showModal: false, //画家详情模态框
artworkModalVisible: false, //画作详情模态框显示
pageType: "view",
rowData: {},
megSendData: {
sendMessageText: "", //输入的文字内容
}, //要发送的聊天信息
latestChatId: "", //最早的聊天记录
newestChatId: "", //最新的聊天记录
userSessionId: "", //用户会话ID
userId: "", //用户Id
hasMoreData: true, //是否还有更多
pageSize: 10, //每页数据量
artistInfo: {}, //画家信息
sessionId: "", //当前用户会话Id
direction: 2, //查询聊天记录的方向1=向后查最新即上拉加载2=向前查最旧,即下拉刷新
focusOn: "", //当前聚焦
sendLoading: false, //发送消息loading
originalCustomerList: [], //存储快照,以用于筛选时恢复
loadPrevChat: false, //是否正在加载之前的聊天内容
hasNomoreChat: false, //是否还有更多聊天记录
chatLongPressTimeout: null, //聊天内容长按延时事件
voiceSrc: "", //语音文件地址
isLongPress: false, // 长按标记
preventDocumentClick: false, // 防止长按松手时关闭弹窗标记
autoGetNewMsg: null, // 自动获取新消息
isShowCreateDialog: false, //是否显示创建会话弹窗
artistSearchConfig: [
{
type: "text",
label: "画家姓名",
field: "ArtistName",
class: "col-4",
placeholder: "",
},
{
type: "text",
label: "画家编号",
field: "Tnum",
class: "col-4",
placeholder: "",
},
], //画家搜索框配置
artistSearchBtnLoading: false, //按钮加载中状态
artistList: [], //的画家列表
pagination: {
current: 1,
pageSize: 5,
total: 0,
showSizePicker: true,
showQuickJumper: true,
pageSizeOptions: [5],
}, //分页配置
searchObj: {
ArtistName: "",
Tnum: "",
}, //搜索内容对象
});
let socket = ref(null);
// 详情数据
let detailInfo = ref(null);
const handleResize = () => {
// console.log("窗口大小已改变:" + window.innerHeight);
state.windowHeight = window.innerHeight;
let serviceAreaHeight =
window.innerHeight -
document.querySelector(".fixed-nav-bar").getBoundingClientRect().height -
70 -
document.querySelector(".fl-mb-xs").getBoundingClientRect().height -
16 -
13;
if (serviceAreaHeight < 300) {
serviceAreaHeight = 300;
}
state.serviceAreaHeight = serviceAreaHeight;
if (state.customerList.length > 0) {
let chatListHeight =
serviceAreaHeight -
document.querySelector(".chat-service-userInfo").getBoundingClientRect()
.height -
document.querySelector(".chat-input-area").getBoundingClientRect()
.height -
35 -
40;
state.chatListHeight = chatListHeight;
} else {
state.chatListHeight = serviceAreaHeight;
}
// console.log(serviceAreaHeight, chatListHeight);
};
const handleKeyDownEnter = (event) => {
if (event.key === "Enter") {
if (state.focusOn == "msgInput" && !state.sendLoading) {
sendMessage(1);
}
}
};
const scrollChatList = ref(false);
onMounted(() => {
initWebSocket();
handleResize();
window.addEventListener("resize", handleResize);
document.addEventListener("keydown", handleKeyDownEnter);
document.addEventListener("click", handleDocumentClick);
customScrollToBottom();
});
onBeforeUnmount(() => {
if (state.autoGetNewMsg) {
clearTimeout(state.autoGetNewMsg);
state.autoGetNewMsg = null;
}
window.removeEventListener("resize", handleResize);
document.removeEventListener("keydown", handleKeyDownEnter);
document.removeEventListener("click", handleDocumentClick);
if (socket && socket.value) {
socket.value.close = () => {
console.log("WebSocket连接已关闭");
};
}
});
//点击聊天内容
const handleChatEachClick = (chatItem) => {
if (state.isLongPress) {
state.isLongPress = false;
return;
}
if (chatItem.message.msgType == 3) {
let audio = document.querySelector("#voiceAudio" + chatItem.ID);
state.voiceSrc = chatItem.message.media[0].url;
audio.load();
if (chatItem.isPlay) {
audio.pause();
chatItem.isPlay = false;
} else {
audio.play();
chatItem.isPlay = true;
setTimeout(() => {
chatItem.isPlay = false;
}, chatItem.message.media[0].duration);
}
}
};
const scrollCustomerList = ref(false);
const scrollCustomerListToTop = () => {
if (scrollCustomerList.value) {
scrollCustomerList.value.scrollTop = 0;
}
};
//点击用户头像
const handleCustomerHeadImgClick = (chatItem) => {
console.log(chatItem);
// state.dialogInitiate = true;
};
//点击显示查看画家信息模态框
const viewArtistInfo = () => {
state.dialogInitiate = false;
state.showModal = true;
};
//点击隐藏查看画家信息模态框
const closeFn = () => {
state.showModal = false;
state.dialogInitiate = true;
};
//点击显示画作详情模态框
const viewArtworkInfo = () => {
state.dialogInitiate = false;
state.artworkModalVisible = true;
};
//点击隐藏画作详情模态框
const handleCloseModal = () => {
state.artworkModalVisible = false;
state.dialogInitiate = true;
};
//点击发送消息
const sendMessage = async (msgType, mediaInfo) => {
state.sendLoading = true;
let params = {
sessionId: state.userSessionId, //会话id
msgType: msgType, //消息类型1=文本 2=图片 3=音频 4=视频
text: msgType == 1 ? state.megSendData.sendMessageText : "", //文本内容
};
if (msgType != 1) {
params.media = [
{
mediaId: mediaInfo.ID,
},
]; //媒体文件列表
} else {
if (!state.megSendData.sendMessageText.trim()) {
state.sendLoading = false;
return;
}
}
console.log(params);
const res = await customerServiceApi.sendNewMessage(params);
if (res.status === 0) {
state.sendLoading = false;
console.log(res);
if (msgType == 1) {
state.megSendData.sendMessageText = "";
}
state.direction = 1;
getMessageList(2);
} else {
state.sendLoading = false;
message.error('res.message || "操作失败",');
}
};
const getUserMessage = async (newMsg) => {
const res = await customerServiceApi.getUserMsgStat({});
if (res && res.status === 0) {
console.log(res);
if (res.data && res.data.length > 0) {
if (Local.get("clearUserList")) {
let clearUserList = Local.get("clearUserList");
if (clearUserList.length > 0) {
res.data.forEach((resItem, resIndex) => {
clearUserList.forEach((item, index) => {
if (item.userId == resItem.userId) {
if (resItem.total > 0) {
clearUserList.splice(index, 1);
console.log(clearUserList);
Local.set("clearUserList", clearUserList);
} else {
resItem.hide = true;
}
}
});
});
res.data = res.data.filter((item) => {
return !item.hide;
});
}
}
res.data.forEach((item, index) => {
item.isActive = false;
});
state.customerList = res.data;
state.originalCustomerList = [...state.customerList];
nextTick(() => {
handleResize();
});
if (state.customerList.length > 0) {
let selectedCustomerIndex = 0;
if (Local.get("selectedCustomerId")) {
let selectedCustomerId = Local.get("selectedCustomerId");
state.customerList.forEach((item, index) => {
if (item.userId == selectedCustomerId) {
selectedCustomerIndex = index;
}
});
}
if (newMsg) {
state.customerList[selectedCustomerIndex].isActive = true;
Local.set(
"selectedCustomerId",
state.customerList[selectedCustomerIndex].userId
);
if (state.customerList[selectedCustomerIndex].total > 0) {
if (!state.userSessionId) {
state.userSessionId =
state.customerList[selectedCustomerIndex].sessionId;
state.userId = state.customerList[selectedCustomerIndex].userId;
getArtistInfo();
}
state.direction = 1;
getMessageList(2);
} else {
if (state.autoGetNewMsg) {
clearTimeout(state.autoGetNewMsg);
state.autoGetNewMsg = null;
}
state.autoGetNewMsg = setTimeout(() => {
getUserMessage(true);
}, 1000 * 60 * 5);
}
} else {
changeCustomer(
state.customerList[selectedCustomerIndex],
selectedCustomerIndex
);
}
}
}
} else {
message.error('res.message || "操作失败",');
}
};
//初始化websocket
const initWebSocket = () => {
let socketUrl = "wss://artist.fontree.cn/api/aschat/ws";
if (import.meta.env.VITE_API_URL === "http://114.218.158.24:9020/") {
socketUrl = "wss://artisttest.fontree.cn/api/aschat/ws";
}
socket.value = new WebSocket(socketUrl);
socket.value.onopen = () => {
console.log("WebSocket连接已建立");
const token = Local.get("token");
console.log(token);
socket.value.send(
JSON.stringify({
type: 5,
content: {
auth: token,
domain: "fontree",
},
})
);
};
socket.value.onmessage = socketMessage;
socket.value.onclose = () => {
console.log("WebSocket连接已关闭");
setTimeout(() => {
socket.value = new WebSocket(socketUrl);
}, 1000);
};
socket.value.onerror = (error) => {
console.error("WebSocket error:", error);
socket.value.close();
setTimeout(() => {
socket.value = new WebSocket(socketUrl);
}, 1000);
};
};
const socketMessage = (event) => {
if (event.data) {
console.log(event);
let result = JSON.parse(event.data);
if (result.type == 0) {
//连接成功
state.sessionId = result.content.sessionId;
getUserMessage();
} else if (result.type == 4) {
//收到新消息
getUserMessage(true);
}
}
};
//点击切换用户
const changeCustomer = (customerItem, customerIndex) => {
state.hasNomoreChat = false;
Local.set("selectedCustomerId", customerItem.userId);
state.customerList.forEach((item, index) => {
item.isActive = false;
});
state.customerList[customerIndex].isActive = true;
state.chatList = [];
state.userSessionId = customerItem.sessionId;
state.userId = customerItem.userId;
getMessageList(1);
getArtistInfo();
// getDetail();
};
//观察新消息是否已读
const observeElementsVisibility = (element, selectedCustomerIndex) => {
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log(`Element ${entry.target.id} is visible in the viewport.`);
state.customerList[selectedCustomerIndex].total =
state.customerList[selectedCustomerIndex].total - 1;
observer.unobserve(element);
} else {
console.log(
`Element ${entry.target.id} is not visible in the viewport.`
);
}
});
});
observer.observe(element);
};
//获取聊天信息
const getMessageList = async (flag) => {
let params = {
sessionId: state.userSessionId,
pageSize: state.pageSize,
};
if (state.chatList.length == 0) {
params.recent = true;
} else {
if (flag == 1) {
//查最近10条
params.recent = true;
} else if (flag == 2) {
//上拉加载——获取当前最后消息往后的所有新消息
params.direction = state.direction;
params.currentId = state.newestChatId;
} else if (flag == 3) {
//下拉刷新——获取当前最前消息往前的所有旧消息
params.direction = state.direction;
params.currentId = state.latestChatId;
}
}
const res = await customerServiceApi.getMessageList(params);
if (res && res.status === 0) {
console.log(res);
if (res.data.length > 0) {
res.data.forEach((item, index) => {
if (item.userId == state.userId) {
item.eventType = "receive";
} else {
item.eventType = "send";
}
if (item?.message?.media[0]?.duration) {
let durationFormat = utils.division(
item.message.media[0].duration,
1000,
0
);
if (durationFormat <= 60) {
durationFormat = durationFormat + "s";
} else if (durationFormat > 60 && durationFormat <= 3600) {
durationFormat =
Math.floor(utils.division(durationFormat, 60)) +
"min" +
(durationFormat % 60) +
"s";
} else if (durationFormat > 3600) {
durationFormat =
Math.floor(utils.division(durationFormat, 3600)) +
"h" +
Math.floor(utils.division(durationFormat % 3600, 60)) +
"min" +
((durationFormat % 3600) % 60) +
"s";
}
item.message.media[0].durationText = durationFormat;
}
});
}
if (res.data.length < state.pageSize) {
state.hasMoreData = false;
} else {
state.hasMoreData = true;
}
if (state.chatList.length == 0) {
state.chatList = res.data;
} else {
if (flag == 1) {
state.chatList = res.data;
} else if (flag == 2) {
state.chatList = state.chatList.concat(res.data);
} else if (flag == 3) {
state.chatList.unshift(...res.data);
}
}
if (state.chatList.length > 0) {
state.latestChatId = state.chatList[0].ID;
state.newestChatId = state.chatList[state.chatList.length - 1].ID;
}
let selectedCustomerIndex = 0;
if (Local.get("selectedCustomerId")) {
let selectedCustomerId = Local.get("selectedCustomerId");
state.customerList.forEach((item, index) => {
if (item.userId == selectedCustomerId) {
selectedCustomerIndex = index;
}
});
}
// if (flag == 2) {
// console.log(res.data);
// if (res.data.length > 0) {
// res.data.forEach((item, index) => {
// nextTick(() => {
// console.log(item.ID);
// let eachChatItem = document.querySelector("#chatEach" + item.ID);
// console.log(eachChatItem);
// const unobserve = observeElementsVisibility(
// eachChatItem,
// selectedCustomerIndex
// );
// });
// });
// }
// } else {
state.customerList[selectedCustomerIndex].total = 0;
// }
if (flag == 3) {
nextTick(() => {
let currentChat = document.querySelector(
"#chatEach" + params.currentId
);
let getHistoryRecordBtn = document.querySelector(
".chat-list-getHistoryRecord-btn"
);
if (scrollChatList.value) {
// console.log(scrollChatList.value.scrollHeight);
if (currentChat) {
scrollChatList.value.scrollTop =
currentChat.offsetTop - getHistoryRecordBtn.offsetHeight;
}
state.loadPrevChat = false;
} else {
state.loadPrevChat = false;
}
if (res.data.length == 0) {
state.hasNomoreChat = true;
} else {
state.hasNomoreChat = false;
}
});
return;
}
customScrollToBottom();
} else {
state.loadPrevChat = false;
message.error('res.message || "操作失败",');
}
if (state.autoGetNewMsg) {
clearTimeout(state.autoGetNewMsg);
state.autoGetNewMsg = null;
}
if (state.customerList.length > 0) {
state.autoGetNewMsg = setTimeout(() => {
getUserMessage(true);
}, 1000 * 60 * 5);
}
if (state.autoGetNewMsg) {
clearTimeout(state.autoGetNewMsg);
state.autoGetNewMsg = null;
}
if (state.customerList.length > 0) {
state.autoGetNewMsg = setTimeout(() => {
getUserMessage(true);
}, 1000 * 60 * 5);
}
};
//点击关闭与对应用户聊天框
const closeChatCustomer = (index) => {
let clearUserList = [];
let update = true;
if (Local.get("clearUserList")) {
clearUserList = Local.get("clearUserList");
clearUserList.forEach((item) => {
if (state.customerList[index].userId == item.userId) {
update = false;
}
});
}
if (update) {
clearUserList.push(state.customerList[index]);
}
Local.set("clearUserList", clearUserList);
state.customerList.splice(index, 1);
if (state.customerList.length > 0) {
changeCustomer(state.customerList[0], 0);
} else {
state.chatList = [];
state.userSessionId = "";
state.userId = "";
Local.set("selectedCustomerId", "");
}
};
//上传图片
const uploadImageChange = (event) => {
// console.log(event)
sendMessage(2, event.data);
};
//滑动到底部
const customScrollToBottom = () => {
setTimeout(() => {
if (scrollChatList.value) {
// console.log(scrollChatList.value.scrollHeight);
scrollChatList.value.scrollTop = scrollChatList.value.scrollHeight;
}
}, 100);
};
//获取画家简要信息
const getArtistInfo = async () => {
let params = {
userId: state.userId,
};
const res = await customerServiceApi.getArtistInfo(params);
if (res && res.status === 0) {
console.log(res);
state.artistInfo = res.data;
state.customerInfoList = [];
Object.keys(state.artistInfo).forEach((item, index) => {
let label;
if (item == "tnum") {
label = "画家编号";
} else if (item == "artistName") {
label = "画家姓名";
} else if (item == "age") {
label = "年龄";
} else if (item == "sex") {
label = "性别";
} else if (item == "nativePlace") {
label = "籍贯";
} else if (item == "telNum") {
label = "电话";
}
if (label) {
state.customerInfoList.push({
label: label,
value: state.artistInfo[item],
});
}
});
} else {
message.error('res.message || "操作失败",');
}
};
//发送消息输入框获得焦点
const inputMsgOnFocus = () => {
state.focusOn = "msgInput";
};
//发送消息输入框失去焦点
const inputMsgOnBlur = () => {
state.focusOn = "";
};
//获取之前的数据
const getHistoryRecord = () => {
if (state.hasNomoreChat || state.loadPrevChat) {
return;
}
state.loadPrevChat = true;
state.direction = 2;
getMessageList(3);
};
//获取画家详细信息
const getDetail = async () => {
await $request.HTTP.artist
.artistDetail({ Uid: String(state.userId) })
.then((res) => {
if (res.status == 0) {
detailInfo.value = res.data;
} else {
message.error(res.msg);
}
})
.catch((e) => {
message.error(e.response.data.msg || "操作失败,请稍后再试");
});
};
//搜索用户
const searchCustomer = (searchText) => {
state.customerList = [...state.originalCustomerList];
if (!searchText.trim()) {
return;
}
if (state.customerList.length > 0) {
state.customerList = state.customerList.filter((item, index) => {
item.isMatch = false;
if (item.name.indexOf(searchText) > -1) {
item.isMatch = true;
}
return item.isMatch;
});
}
};
//长按聊天内容展示上方弹窗
const showChatToolTip = (chatItem) => {
if (chatItem.message.msgType == 3) {
chatItem.showToolTip = true;
}
};
//处理鼠标按下聊天内容事件
const handleMouseDownChatItem = (e, chatItem) => {
// e.stopPropagation();
// e.preventDefault();
state.chatLongPressTimeout = setTimeout(() => {
state.isLongPress = true;
state.preventDocumentClick = true;
showChatToolTip(chatItem);
}, 500);
};
//处理鼠标抬起聊天内容事件
const handleMouseUpChatItem = (e) => {
if (state.chatLongPressTimeout) {
clearTimeout(state.chatLongPressTimeout);
state.chatLongPressTimeout = null;
}
setTimeout(() => {
state.preventDocumentClick = false;
}, 0);
};
//处理鼠标离开聊天内容事件
const handleMouseOutChatItem = (e) => {
if (state.chatLongPressTimeout) {
clearTimeout(state.chatLongPressTimeout);
state.chatLongPressTimeout = null;
}
state.isLongPress = false;
};
//点击聊天上方弹窗按钮
const handleToolTipClick = async (chatItem, event) => {
event.stopPropagation();
if (chatItem?.message?.media[0]?.convText) {
chatItem.voiceToText = chatItem.message.media[0].convText;
chatItem.showToolTip = false;
} else {
let params = {
mediaId: chatItem.message.media[0].mediaId,
};
// console.log(params);
const res = await customerServiceApi.voiceToText(params);
if (res?.status === 0) {
chatItem.voiceToText = res.data.convText;
chatItem.showToolTip = false;
}
}
};
// 处理document点击事件
const handleDocumentClick = () => {
if (state.preventDocumentClick) {
return;
}
if (state.chatList && state.chatList.length > 0) {
state.chatList.forEach((item) => {
if (item.showToolTip) {
item.showToolTip = false;
}
});
}
};
//点击显示创建会话弹窗
const showCreateDialog = () => {
state.isShowCreateDialog = true;
};
//处理搜索
const handleArtistSearchChange = ({ obj }) => {
console.log(obj);
state.searchObj = obj;
state.pagination.current = 1;
getArtistSearchResultList();
};
const handlePagination = (page) => {
state.pagination.current = page;
getArtistSearchResultList();
};
const handlePaginationSize = (pageSize) => {
state.pagination.pageSize = pageSize;
getArtistSearchResultList();
};
//获取画家搜索结果列表
const getArtistSearchResultList = async () => {
state.artistSearchBtnLoading = true;
let params = {
artistName: state.searchObj.ArtistName, //画家姓名
tnum: state.searchObj.Tnum, //画家编号
page: state.pagination.current,
pageSize: state.pagination.pageSize,
isRealName: 1,
idname: true,
};
const res = await customerServiceApi.getArtistSearchResultList(params);
if (res && res.status === 0) {
state.artistSearchBtnLoading = false;
console.log(res);
state.artistList = res.data.Data;
state.pagination.total = res.data.Total;
if (state?.artistList?.length > 0) {
state.artistList.forEach((artistItem) => {
const artistInfoList = [];
const fieldOrder = [
{
key: "realName",
label: "画家姓名",
process: (v) => v.replace(/#/g, ""),
},
{ key: "sex", label: "性别" },
{ key: "tnum", label: "画家编号" },
{ key: "age", label: "年龄" },
{ key: "telNum", label: "手机号" },
{ key: "nativePlace", label: "籍贯" },
];
fieldOrder.forEach(({ key, label, process }) => {
if (artistItem.hasOwnProperty(key)) {
let value = artistItem[key];
if (process) value = process(value);
artistInfoList.push({ label, value });
}
});
artistItem.artistInfoList = artistInfoList;
});
}
} else {
state.artistSearchBtnLoading = false;
message.error('res.message || "操作失败",');
}
};
//点击开始会话
const createDialog = (artistInfo) => {
state.isShowCreateDialog = false;
let hadChat = false;
if (state?.customerList?.length > 0) {
state.customerList.forEach((item, index) => {
if (item.userId === artistInfo.userId) {
// console.log(state.customerList[index],index)
hadChat = true;
changeCustomer(state.customerList[index], index);
}
});
}
if (!hadChat) {
state.customerList.unshift({
artistUid: artistInfo.artistUid,
name: artistInfo.realName.replace(/#/g, ""),
sessionId: artistInfo.userId.toString(),
total: 0,
userId: artistInfo.userId,
});
changeCustomer(state.customerList[0], 0);
}
if (Local.get("clearUserList")) {
let clearUserList = Local.get("clearUserList");
if (clearUserList.length > 0) {
clearUserList.forEach((item, index) => {
if (item.userId == artistInfo.userId) {
clearUserList.splice(index, 1);
console.log(clearUserList);
Local.set("clearUserList", clearUserList);
}
});
}
}
state.searchObj = {
ArtistName: "",
Tnum: "",
};
};
</script>
<style scoped lang="scss">
.customer-service-page {
:deep(.fl-px-md) {
width: unset !important;
text-align: left !important;
padding: 0 !important;
margin: 0 40px 0 0;
}
:deep(.fl-mt-md) {
padding: 0 !important;
}
:deep(.fl-mt-sm) {
width: 368px !important;
}
.customer-service {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
.customer-list-area {
height: 100%;
position: relative;
box-sizing: border-box;
.customer-list::-webkit-scrollbar {
width: 0;
}
.customer-list::-webkit-scrollbar {
height: 0;
}
.customer-list {
padding: 13px 21px 76px;
border-right: 1px solid #e0e0e5;
height: 100%;
overflow: scroll;
box-sizing: border-box;
width: 238px;
.customer-list-each {
width: 186px;
height: 42px;
border: 1px solid #e0e0e5;
border-radius: 3px;
margin: 0 0 10px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: relative;
cursor: pointer;
.customer-list-each-leftLine {
width: 5px;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
span {
color: #343639;
line-height: 1;
}
.customer-list-each-unread-msg-count {
width: 14px;
height: 14px;
border-radius: 50%;
background-color: #cf3050;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin: 0 0 0 4px;
span {
color: #fff;
font-size: 10px;
font-weight: 500;
}
}
img {
position: absolute;
right: 10px;
top: 50%;
margin-top: -8px;
width: 16px;
height: 16px;
}
}
}
.customer-list-back-to-top-btn {
position: absolute;
right: 23px;
bottom: 23px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0px 3px 6px 1px rgba(0, 0, 0, 0.12);
width: 48px;
height: 48px;
border-radius: 50%;
background-color: #fff;
cursor: pointer;
img {
width: 16px;
height: 16px;
margin: 4px 0 0;
}
span {
font-size: 10px;
color: #46299d;
margin: 2px 0 0;
}
}
}
.chat-service {
position: relative;
width: 100%;
padding: 13px 22px 22px 22px;
.chat-service-userInfo {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
background-color: #f9f9fd;
width: 100%;
.chat-service-userInfo-detail {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
flex: 1;
padding: 18px 22px;
.chat-service-userInfo-detail-each {
width: calc(100% / 3);
display: flex;
flex-direction: row;
min-width: 132px;
span {
line-height: 1;
}
}
.chat-service-userInfo-detail-each span:nth-child(1) {
margin: 0 10px 0 0;
min-width: 70px;
display: inline-block;
}
}
.chat-service-userInfo-photo {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
padding: 18px 22px;
flex-shrink: 0;
span {
margin: 0 10px 0 0;
min-width: 70px;
display: inline-block;
}
}
}
.chat-list::-webkit-scrollbar {
width: 0;
}
.chat-list::-webkit-scrollbar {
height: 0;
}
.chat-list {
position: relative;
overflow: scroll;
margin: 20px 0;
.chat-list-getHistoryRecord-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 0 20px;
cursor: pointer;
@keyframes chatLoading {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
.getHistoryRecord-loading {
width: 19px;
height: 19px;
margin: 0 0 7px;
animation-name: chatLoading;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-duration: 2s;
}
.getHistoryRecord-arrow {
width: 19px;
height: 11px;
margin: 0 0 7px;
}
span {
color: #46299d;
}
}
.chat-tool-tip {
position: absolute;
top: -50px;
background-color: #0000;
z-index: 10036;
background-color: #505050;
border-radius: 10px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
.chat-tool-tip-each {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
:deep(.n-button .n-button__border) {
border: 0 !important;
}
span {
color: #e0e0e0;
line-height: 1;
}
}
}
.chat-tool-tip::after {
content: "";
position: absolute;
background-color: #505050;
width: 14px;
height: 14px;
bottom: -4px;
left: 50%;
margin-left: -7px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
border-radius: 2px;
z-index: -1;
}
.chat-list-each {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
margin: 0 0 38px;
position: relative;
.chat-each-headImg {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: 50%;
flex-shrink: 0;
background-color: #46299d;
span {
color: #fff;
line-height: 1;
}
}
.chat-each-content {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
margin: 0 10px;
.chat-each-details {
box-shadow: 0 3px 6px 1px rgba(0, 0, 0, 0.08);
max-width: 452px;
padding: 14px 10px;
border-radius: 0 8px 8px 8px;
margin: 0 0 6px;
background-color: #b5a9d9;
box-sizing: border-box;
span {
color: #fff;
line-height: 1;
}
.chat-voice-icon {
width: 14px;
height: 20px;
margin: 0 0 0 10px;
}
.chat-voice-icon-leftAlign {
transform: rotateZ(180deg);
margin: 0 10px 0 0;
}
}
.chat-each-details-leftAlign {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
max-width: 304px;
cursor: pointer;
}
.chat-each-dateTime {
span {
color: #bababa;
line-height: 27px;
}
}
}
}
.chat-list-each-rightAlign {
justify-content: flex-end;
.chat-each-headImg {
}
.chat-each-content {
align-items: flex-end;
.chat-each-details {
background-color: #fff;
border-radius: 8px 0 8px 8px;
span {
color: #1a1a1a;
}
}
.chat-each-details-rightAlign {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
max-width: 304px;
cursor: pointer;
}
.chat-each-dateTime {
}
}
}
}
.chat-input-area {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
position: absolute;
left: 22px;
bottom: 22px;
width: calc(100% - 44px);
box-sizing: border-box;
:deep(.ant-upload-select-picture-card) {
border: 0;
width: 37px;
height: 34px;
margin: 0 20px 0 0;
}
img {
width: 37px;
height: 34px;
margin: 0 20px 0 0;
}
.chat-input-area-send-btn {
margin: 0 0 0 20px;
width: 161px;
height: 34px;
}
}
}
}
}
.customer-service-modal-body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 42px 0 65px;
.modal-body-btns {
width: 332px;
height: 42px;
}
.view-artist-btn {
}
.view-artwork-btn {
margin: 36px 0 0;
}
}
.chat-no-data {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
margin: 0 0 20px 0;
max-width: 190px;
max-height: 190px;
}
span {
font-size: 16px;
font-weight: 500;
color: #cdcdcd;
}
}
.artist-search-area {
:deep(.fl-px-md) {
width: 94px !important;
text-align: left !important;
padding: 0 !important;
}
:deep(.fl-mt-sm) {
width: calc(100% / 3 - 20px) !important;
margin: 0 20px 0 0;
}
:deep(.fl-mt-sm:nth-last-child(1)) {
margin: 0;
}
.artist-list {
height: 517px;
position: relative;
.artist-list-loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(224, 224, 224, 0.4);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
@keyframes chatLoading {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
.artist-list-loading-image {
width: 19px;
height: 19px;
margin: 0 0 7px;
animation-name: chatLoading;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-duration: 2s;
}
}
.artist-list-each {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
background-color: #f9f9fd;
width: 100%;
margin: 10px 0 0;
.artist-info-detail {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
flex: 1;
padding: 18px 22px;
.artist-info-detail-each {
width: calc(100% / 3);
display: flex;
flex-direction: row;
min-width: 132px;
span {
line-height: 1;
}
}
.artist-info-detail-each span:nth-child(1) {
margin: 0 10px 0 0;
min-width: 70px;
display: inline-block;
}
}
.artist-info-photo {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
padding: 18px 22px;
flex-shrink: 0;
span {
margin: 0 10px 0 0;
min-width: 70px;
display: inline-block;
}
:deep(.ant-image-img) {
object-fit: cover;
}
}
.artist-start-dialog-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
cursor: pointer;
margin: 0 90px;
span {
font-size: 14rpx;
font-weight: 400;
line-height: 20px;
color: #46299d;
}
}
}
}
.artist-list-pagination {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
padding: 8px 0 0;
}
.no-artist {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 208px 0 236px;
img {
width: 83px;
height: 83px;
}
text {
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: #c2c2c2;
margin: 8px 0 0;
}
}
}
</style>