You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1197 lines
53 KiB
1197 lines
53 KiB
define(['moment', 'moment/locale/zh-cn'], function (Moment) {
|
|
|
|
// 表情数据
|
|
const emojiList = [
|
|
{ "name": "[笑掉牙]", "file": "xiaodiaoya.png" },
|
|
{ "name": "[可爱]", "file": "keai.png" },
|
|
{ "name": "[冷酷]", "file": "lengku.png" },
|
|
{ "name": "[闭嘴]", "file": "bizui.png" },
|
|
{ "name": "[生气]", "file": "shengqi.png" },
|
|
{ "name": "[惊恐]", "file": "jingkong.png" },
|
|
{ "name": "[瞌睡]", "file": "keshui.png" },
|
|
{ "name": "[大笑]", "file": "daxiao.png" },
|
|
{ "name": "[爱心]", "file": "aixin.png" },
|
|
{ "name": "[坏笑]", "file": "huaixiao.png" },
|
|
{ "name": "[飞吻]", "file": "feiwen.png" },
|
|
{ "name": "[疑问]", "file": "yiwen.png" },
|
|
{ "name": "[开心]", "file": "kaixin.png" },
|
|
{ "name": "[发呆]", "file": "fadai.png" },
|
|
{ "name": "[流泪]", "file": "liulei.png" },
|
|
{ "name": "[汗颜]", "file": "hanyan.png" },
|
|
{ "name": "[惊悚]", "file": "jingshu.png" },
|
|
{ "name": "[困~]", "file": "kun.png" },
|
|
{ "name": "[心碎]", "file": "xinsui.png" },
|
|
{ "name": "[天使]", "file": "tianshi.png" },
|
|
{ "name": "[晕]", "file": "yun.png" },
|
|
{ "name": "[啊]", "file": "a.png" },
|
|
{ "name": "[愤怒]", "file": "fennu.png" },
|
|
{ "name": "[睡着]", "file": "shuizhuo.png" },
|
|
{ "name": "[面无表情]", "file": "mianwubiaoqing.png" },
|
|
{ "name": "[难过]", "file": "nanguo.png" },
|
|
{ "name": "[犯困]", "file": "fankun.png" },
|
|
{ "name": "[好吃]", "file": "haochi.png" },
|
|
{ "name": "[呕吐]", "file": "outu.png" },
|
|
{ "name": "[龇牙]", "file": "ziya.png" },
|
|
{ "name": "[懵比]", "file": "mengbi.png" },
|
|
{ "name": "[白眼]", "file": "baiyan.png" },
|
|
{ "name": "[饿死]", "file": "esi.png" },
|
|
{ "name": "[凶]", "file": "xiong.png" },
|
|
{ "name": "[感冒]", "file": "ganmao.png" },
|
|
{ "name": "[流汗]", "file": "liuhan.png" },
|
|
{ "name": "[笑哭]", "file": "xiaoku.png" },
|
|
{ "name": "[流口水]", "file": "liukoushui.png" },
|
|
{ "name": "[尴尬]", "file": "ganga.png" },
|
|
{ "name": "[惊讶]", "file": "jingya.png" },
|
|
{ "name": "[大惊]", "file": "dajing.png" },
|
|
{ "name": "[不好意思]", "file": "buhaoyisi.png" },
|
|
{ "name": "[大闹]", "file": "danao.png" },
|
|
{ "name": "[不可思议]", "file": "bukesiyi.png" },
|
|
{ "name": "[爱你]", "file": "aini.png" },
|
|
{ "name": "[红心]", "file": "hongxin.png" },
|
|
{ "name": "[点赞]", "file": "dianzan.png" },
|
|
{ "name": "[恶魔]", "file": "emo.png" }
|
|
]
|
|
|
|
// 通知消息
|
|
function audioPlay(type) {
|
|
let ex = new Audio(`/assets/addons/shopro/img/chat/${type}.mp3`);
|
|
if (ex) {
|
|
ex.play();
|
|
}
|
|
}
|
|
|
|
// 客服错误通知
|
|
function callBackNotice(res, flag = false) {
|
|
flag &&
|
|
res.msg &&
|
|
ElementPlus.ElNotification({
|
|
title: 'socket',
|
|
message: `客服错误:${res.msg}`,
|
|
showClose: true,
|
|
type: res.code == 1 ? 'success' : 'warning',
|
|
duration: 3000,
|
|
});
|
|
}
|
|
|
|
const IS_DEBUG = true; // DEBUG开关
|
|
|
|
const debug = (...args) => {
|
|
if (IS_DEBUG) {
|
|
console.info('%c%s', 'color: blue; background: yellow; font-size: 11px;', ...args);
|
|
}
|
|
return;
|
|
};
|
|
|
|
// 重新连接尝试次数
|
|
const reconnectionAttempts = 5; //次
|
|
// 重新连接间隔时间
|
|
const reconnectionDelay = 10; // 秒
|
|
|
|
const SaChat = {
|
|
template: `#saChatTemplate`,
|
|
props: {},
|
|
setup(props) {
|
|
|
|
const { ref, reactive, computed, getCurrentInstance, nextTick } = Vue
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
|
const chat = {
|
|
state: reactive({
|
|
config: {}, // 配置信息
|
|
chatService: null, // 示例
|
|
sessionList: [], // 会话列表
|
|
customerServicesList: [], // 客服列表
|
|
sessionType: 'ing', // 列表状态 ing=会话中|waiting=排队中| history=历史
|
|
customerOnLineList: [], // 会话中
|
|
customerWaitingList: [], // 排队中
|
|
customerHistoryList: [], // 历史
|
|
chatList: [], // 会话信息
|
|
commonWords: [], // 常用语列表
|
|
currentCustomerService: {}, // 当前客服信息
|
|
customerServiceIdentityList: [], // 客服身份列表
|
|
currentCustomer: null, // 当前顾客信息
|
|
historyPagination: {
|
|
page: 0,
|
|
list_rows: 10,
|
|
last_id: 0,
|
|
lastPage: 0,
|
|
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
|
|
},
|
|
connection: {
|
|
status: '',
|
|
attempts: 0,
|
|
reconnectionAttempts,
|
|
delay: 0,
|
|
showTip: true,
|
|
isFlag: false,
|
|
}, // 连接状态
|
|
isSendSucces: 0, // 是否发送成功 -1=发送中|0=发送成功|1发送失败
|
|
|
|
notificationType: '', // 站内信类型
|
|
notificationTypeList: [], // 站内信类型列表
|
|
notificationList: [], // 站内信列表
|
|
}),
|
|
|
|
// 打开客服弹窗
|
|
open_chat() {
|
|
chat.state.currentCustomer = chat.state.customerOnLineList[0];
|
|
chat.state.customerOnLineList.length && chat.socket_change_customer_list({
|
|
session: chat.state.sessionType,
|
|
})
|
|
},
|
|
|
|
// 初始化chat配置请求
|
|
async chatInit() {
|
|
Fast.api.ajax({
|
|
url: 'shopro/chat/index/init',
|
|
type: 'GET',
|
|
}, function (ret, res) {
|
|
chat.state.config = res.data;
|
|
chat.socket_init(chat.state.config.chat_domain);
|
|
return false
|
|
}, function (ret, res) { })
|
|
},
|
|
|
|
// 1.初始化socket
|
|
async socket_init(connectionUrl, options) {
|
|
chat.state.connection.status = 'connecting';
|
|
chat.state.connection.isFlag = true
|
|
// 连接
|
|
try {
|
|
chat.state.chatService = io(connectionUrl, {
|
|
reconnection: true, // 默认 true 是否断线重连
|
|
reconnectionAttempts: reconnectionAttempts, // 默认无限次 断线尝试次数
|
|
reconnectionDelay: reconnectionDelay * 1000, // 默认 1000,进行下一次重连的间隔。
|
|
reconnectionDelayMax: reconnectionDelay * 1000, // 默认 5000, 重新连接等待的最长时间 默认 5000
|
|
randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间
|
|
timeout: 20000, // 默认 20s
|
|
transports: ['websocket', 'polling'], // websocket | polling,
|
|
...options,
|
|
});
|
|
} catch (error) {
|
|
debug('Socket连接错误', error);
|
|
}
|
|
|
|
// 连接成功
|
|
chat.state.chatService.on('connect', (res) => {
|
|
debug('connect', res);
|
|
chat.state.connection.status = 'connect';
|
|
chat.socket_connection();
|
|
});
|
|
|
|
// 监听会话
|
|
chat.state.chatService.on('message', (res) => {
|
|
debug('message', res);
|
|
if (res.code == 1) {
|
|
audioPlay('chat');
|
|
const { message, sender } = res.data;
|
|
if (chat.state.currentCustomer && chat.state.currentCustomer?.session_id == sender.session_id) {
|
|
// 如果是当前会话,数字角标不增加,chatList,push
|
|
chat.state.chatList.push(message);
|
|
scrollBottom();
|
|
} else {
|
|
// 非当前会话,顾客角标+1
|
|
const index = chat.state.customerOnLineList.findIndex(
|
|
(item) => item.session_id == sender.session_id,
|
|
);
|
|
if (~index) chat.state.customerOnLineList[index].unread_num += 1;
|
|
}
|
|
}
|
|
});
|
|
|
|
// 用户上线
|
|
chat.state.chatService.on('customer_online', (res) => {
|
|
res.code == 1 && chat.socket_change_customer_state(res.data);
|
|
});
|
|
|
|
// 用户下线
|
|
chat.state.chatService.on('customer_offline', (res) => {
|
|
res.code == 1 && chat.socket_change_customer_state(res.data);
|
|
});
|
|
|
|
// 用户被接入
|
|
chat.state.chatService.on('customer_accessed', (res) => {
|
|
if (res.code == 1) {
|
|
const { session_id, chat_user } = res.data;
|
|
const index = chat.state.customerWaitingList.findIndex((item) => item.session_id == session_id);
|
|
index >= 0 && chat.state.customerWaitingList.splice(index, 1);
|
|
if (chat.state.sessionType == 'waiting') {
|
|
chat.state.currentCustomer =
|
|
chat.state.customerWaitingList.length >= 1
|
|
? chat.state.customerWaitingList[0]
|
|
: chat.socket_reset();
|
|
}
|
|
}
|
|
});
|
|
|
|
// 新用户接入
|
|
chat.state.chatService.on('customer_access', (res) => {
|
|
if (res.code == 1) {
|
|
const { session_id, chat_user } = res.data;
|
|
const index = chat.state.customerOnLineList.findIndex((item) => item.session_id == session_id);
|
|
// 如果用户存在,修改会话中用户状态
|
|
if (index >= 0) {
|
|
chat.state.customerOnLineList[index].status = chat_user.status;
|
|
} else {
|
|
// 如果用户不存在
|
|
chat.state.customerOnLineList.unshift(chat_user);
|
|
// 如果当前会话列表有且只有一个新增会话
|
|
if (chat.state.customerOnLineList.length > 0 && chat.state.sessionType == 'ing') {
|
|
chat.state.currentCustomer = chat_user;
|
|
chat.socket_customer_history();
|
|
}
|
|
}
|
|
|
|
callBackNotice(res);
|
|
}
|
|
});
|
|
|
|
// 更新客服列表customer_service_update
|
|
chat.state.chatService.on('customer_service_update', (res) => {
|
|
if (res.code == 1) {
|
|
chat.state.customerServicesList = res.data.customer_services.map((item) => ({
|
|
label: item.name,
|
|
value: item.id,
|
|
}));
|
|
}
|
|
});
|
|
|
|
// 新顾客等待,更新等待列表
|
|
chat.state.chatService.on('customer_waiting', (res) => {
|
|
if (res.code == 1) {
|
|
chat.state.customerWaitingList = res.data.waitings;
|
|
audioPlay('chat');
|
|
if (chat.state.sessionType == 'waiting') {
|
|
chat.state.sessionList = res.data.waitings;
|
|
chat.state.currentCustomer = chat.state.sessionList.length
|
|
? res.data.waitings[0]
|
|
: chat.socket_reset();
|
|
}
|
|
}
|
|
});
|
|
|
|
// 消息通知
|
|
chat.state.chatService.on('notification', (res) => {
|
|
if (res.code == 1) {
|
|
audioPlay('notice');
|
|
if (chat.state.notificationType == res.data.notification_type) {
|
|
chat.state.notificationList.unshift(res.data);
|
|
} else {
|
|
chat.state.notificationTypeList.map((item) => {
|
|
if (item.value == res.data.notification_type) {
|
|
item.unread_num += 1;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// 自定义错误
|
|
chat.state.chatService.on('custom_error', (error) => {
|
|
callBackNotice(error, true);
|
|
});
|
|
|
|
chat.state.chatService.on('error', (error) => {
|
|
chat.state.connection.status = 'error';
|
|
debug('error', error);
|
|
scrollBottom();
|
|
});
|
|
|
|
// 连接失败
|
|
chat.state.chatService.on('connect_error', (error) => {
|
|
debug('connect_error:', error);
|
|
scrollBottom();
|
|
});
|
|
|
|
// 连接超时
|
|
chat.state.chatService.on('connect_timeout', (error) => {
|
|
debug('connect_timeout:', error);
|
|
});
|
|
|
|
// 断开连接
|
|
chat.state.chatService.on('disconnect', (error) => {
|
|
debug('disconnect:', error);
|
|
});
|
|
|
|
// 服务重启重连上reconnect
|
|
chat.state.chatService.on('reconnect', (error) => {
|
|
debug('disconnect:', error);
|
|
});
|
|
|
|
// 尝试重连
|
|
chat.state.chatService.on('reconnect_attempt', (counter) => {
|
|
debug('reconnect_attempt', counter);
|
|
chat.state.connection.status = 'reconnect_attempt';
|
|
});
|
|
|
|
// 重新连接中
|
|
chat.state.chatService.on('reconnecting', (counter) => {
|
|
debug('reconnecting', counter);
|
|
chat.state.connection.status = 'reconnecting';
|
|
chat.state.connection.attempts = counter;
|
|
});
|
|
|
|
// 重连失败
|
|
chat.state.chatService.on('reconnect_error', (error) => {
|
|
debug('reconnect_error', error);
|
|
if (chat.state.connection.attempts >= chat.state.connection.reconnectionAttempts) {
|
|
return;
|
|
}
|
|
chat.state.connection.status = 'reconnect_error';
|
|
// 设置倒计时
|
|
chat.state.connection.delay = reconnectionDelay;
|
|
const timer = setInterval(() => {
|
|
chat.state.connection.delay -= 1;
|
|
if (chat.state.connection.delay <= 1) {
|
|
clearInterval(timer);
|
|
}
|
|
}, 1000);
|
|
});
|
|
|
|
// 重连失败 达到最大重试次数
|
|
chat.state.chatService.on('reconnect_failed', () => {
|
|
debug('reconnect_failed');
|
|
chat.state.connection.status = 'reconnect_failed';
|
|
chat.state.isSendSucces = 1;
|
|
chat.state.connection.isFlag = false
|
|
});
|
|
|
|
// 与服务器连接失败
|
|
chat.state.chatService.on('connect_failed', (error) => {
|
|
debug('connect_failed', error);
|
|
});
|
|
},
|
|
|
|
// socket 连接
|
|
socket_connection(token) {
|
|
chat.state.chatService.emit(
|
|
'connection',
|
|
{
|
|
auth: 'admin',
|
|
token: chat.state.config.token,
|
|
},
|
|
(res) => {
|
|
debug('socket_connection:', res);
|
|
if (res.code == 1) {
|
|
chat.socket_check_identify();
|
|
}
|
|
},
|
|
);
|
|
},
|
|
|
|
// 检测是否是客服 获取客服身份
|
|
socket_check_identify() {
|
|
chat.state.chatService.emit('check_identify', {}, (res) => {
|
|
debug('socket_check_identify:', res);
|
|
if (res.code == 1) {
|
|
chat.socket_customer_login(res.data.customer_services[0]);
|
|
chat.state.customerServiceIdentityList = res.data.customer_services.map((item) => ({
|
|
label: item.name,
|
|
value: item.id,
|
|
room_id: item.room_id,
|
|
}));
|
|
}
|
|
});
|
|
},
|
|
|
|
// 客服登录
|
|
socket_customer_login(data) {
|
|
chat.state.chatService.emit(
|
|
'customer_service_login',
|
|
{
|
|
room_id: data?.room_id || 'admin',
|
|
},
|
|
(res) => {
|
|
debug('customer_service_login:', res);
|
|
if (res.code == 1) {
|
|
chat.state.currentCustomerService = res.data.customer_service;
|
|
chat.state.commonWords = res.data.common_words;
|
|
chat.socket_customer_init();
|
|
}
|
|
callBackNotice(res);
|
|
},
|
|
);
|
|
},
|
|
// 初始化客服
|
|
socket_customer_init() {
|
|
chat.state.chatService.emit('customer_service_init', {}, (res) => {
|
|
if (res.code == 1) {
|
|
chat.state.customerOnLineList = res.data.onlines;
|
|
chat.state.customerWaitingList = res.data.waitings;
|
|
if (chat.state.customerWaitingList.length > 0) {
|
|
// audioPlay('chat');
|
|
}
|
|
chat.state.customerHistoryList = res.data.histories;
|
|
chat.state.sessionList = res.data.onlines;
|
|
// 当前顾客选中
|
|
if (chat.state.sessionList.length) {
|
|
if (!currentSessionTypeIndexs.hasOwnProperty(chat.state.sessionType)) {
|
|
currentSessionTypeIndexs[chat.state.sessionType] = 0;
|
|
}
|
|
}
|
|
chat.socket_change_customer_list({
|
|
session: chat.state.sessionType,
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
// 切换客服身份
|
|
socket_change_customer_identity(data) {
|
|
// 退出客服
|
|
chat.socket_logout_customer();
|
|
// 登录客服
|
|
let info = chat.state.customerServiceIdentityList.filter((item) => data == item.value);
|
|
chat.socket_customer_login(info[0]);
|
|
// 初始化客服
|
|
},
|
|
|
|
// 切换会话列表
|
|
socket_change_customer_list(data) {
|
|
chat.socket_reset();
|
|
chat.state.sessionType = data.session;
|
|
switch (data.session) {
|
|
case 'ing':
|
|
chat.state.sessionList = chat.state.customerOnLineList;
|
|
break;
|
|
case 'waiting':
|
|
chat.state.sessionList = chat.state.customerWaitingList;
|
|
break;
|
|
case 'history':
|
|
chat.state.sessionList = chat.state.customerHistoryList;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!data.type) {
|
|
// 默认显示第一个人的会话信息
|
|
chat.state.sessionList.length && chat.socket_change_customer_info(data.index || 0);
|
|
}
|
|
},
|
|
|
|
// 修改顾客信息,获取历史记录
|
|
socket_change_customer_info(index = 0) {
|
|
chat.socket_reset();
|
|
chat.state.currentCustomer = chat.state.sessionList[index];
|
|
if (chat.state.sessionList[index]) {
|
|
chat.state.sessionList[index].unread_num = 0;
|
|
}
|
|
chat.socket_customer_history();
|
|
},
|
|
|
|
// 重置
|
|
socket_reset() {
|
|
chat.state.currentCustomer = null;
|
|
chat.state.chatList = [];
|
|
chat.state.historyPagination = {
|
|
page: 0,
|
|
list_rows: 10,
|
|
last_id: 0,
|
|
totalPage: 0,
|
|
loadStatus: 'loadmore',
|
|
};
|
|
},
|
|
|
|
// 退出客服
|
|
socket_logout_customer() {
|
|
debug('socket_logout_start');
|
|
chat.state.chatService.emit('customer_service_logout', {}, (res) => {
|
|
debug('socket_logout_res:', res);
|
|
});
|
|
},
|
|
|
|
// 获取用户历史消息
|
|
socket_customer_history() {
|
|
if (!chat.state.currentCustomer) return;
|
|
chat.state.historyPagination.loadStatus = 'loading';
|
|
chat.state.historyPagination.page += 1;
|
|
chat.state.chatService.emit(
|
|
'messages',
|
|
{
|
|
session_id: chat.state.currentCustomer?.session_id || 0,
|
|
...chat.state.historyPagination,
|
|
},
|
|
(res) => {
|
|
if (res.code == 1) {
|
|
chat.state.historyPagination.total = res.data.messages.total;
|
|
chat.state.historyPagination.lastPage = res.data.messages.last_page;
|
|
chat.state.historyPagination.page = res.data.messages.current_page;
|
|
if (res.data.messages.current_page == 1) {
|
|
chat.state.chatList = [];
|
|
}
|
|
res.data.messages.data.forEach((item) => {
|
|
chat.state.chatList.unshift(item);
|
|
});
|
|
chat.state.historyPagination.loadStatus =
|
|
chat.state.historyPagination.page < chat.state.historyPagination.lastPage
|
|
? 'loadmore'
|
|
: 'nomore';
|
|
chat.state.historyPagination.page == 1 && scrollBottom();
|
|
if (chat.state.historyPagination.last_id == 0) {
|
|
chat.state.historyPagination.last_id = res.data.messages.data.length
|
|
? res.data.messages.data[0].id
|
|
: 0;
|
|
}
|
|
}
|
|
},
|
|
);
|
|
},
|
|
|
|
// 修改顾客状态
|
|
socket_change_customer_state(data) {
|
|
const { session_id, chat_user } = data;
|
|
let waitingIndex = -1,
|
|
onlineIndex = -1,
|
|
historyIndex = -1;
|
|
|
|
if (chat.state.customerWaitingList.length) {
|
|
waitingIndex = chat.state.customerWaitingList.findIndex((item) => item.session_id == session_id);
|
|
if (waitingIndex >= 0) {
|
|
chat.state.customerWaitingList[waitingIndex].status = chat_user.status;
|
|
}
|
|
}
|
|
if (chat.state.customerOnLineList.length) {
|
|
onlineIndex = chat.state.customerOnLineList.findIndex((item) => item.session_id == session_id);
|
|
if (onlineIndex >= 0) {
|
|
chat.state.customerOnLineList[onlineIndex].status = chat_user.status;
|
|
}
|
|
}
|
|
if (chat.state.customerHistoryList.length) {
|
|
historyIndex = chat.state.customerHistoryList.findIndex((item) => item.session_id == session_id);
|
|
if (historyIndex >= 0) {
|
|
chat.state.customerHistoryList[historyIndex].status = chat_user.status;
|
|
}
|
|
}
|
|
},
|
|
|
|
// 发送消息
|
|
socket_send(data) {
|
|
// 给会话列表
|
|
chat.state.isSendSucces = -1;
|
|
chat.state.chatList.push(data);
|
|
// 给socket
|
|
chat.state.chatService.emit(
|
|
'message',
|
|
{
|
|
session_id: chat.state.currentCustomer?.session_id || 0,
|
|
message: {
|
|
message: data.message,
|
|
message_type: data.message_type,
|
|
},
|
|
},
|
|
(res) => {
|
|
chat.state.isSendSucces = res.error;
|
|
},
|
|
);
|
|
},
|
|
|
|
// 断开,删除顾客
|
|
socket_change_customer(session_id, index, sessionType, is_del_record) {
|
|
if (sessionType == 'ing') {
|
|
chat.state.chatService.emit('break_customer', { session_id }, (res) => {
|
|
if (res.code == 1) {
|
|
chat.state.customerOnLineList.splice(index, 1);
|
|
chat.socket_reset();
|
|
}
|
|
callBackNotice(res);
|
|
});
|
|
}
|
|
if (sessionType == 'history') {
|
|
chat.state.chatService.emit('del_customer', { session_id, is_del_record }, (res) => {
|
|
if (res.code == 1) {
|
|
chat.state.customerHistoryList.splice(index, 1);
|
|
chat.socket_reset();
|
|
}
|
|
callBackNotice(res);
|
|
});
|
|
}
|
|
},
|
|
|
|
// 切换客服状态
|
|
socket_change_customer_service_status(status) {
|
|
console.log(chat.state, 'chat.state')
|
|
// return
|
|
switch (status) {
|
|
case 'online':
|
|
chat.state.chatService.emit('customer_service_online', {}, (res) => {
|
|
changeBack(res);
|
|
});
|
|
break;
|
|
case 'busy':
|
|
chat.state.chatService.emit('customer_service_busy', {}, (res) => {
|
|
changeBack(res);
|
|
});
|
|
chat.state.sessionList = chat.state.customerWaitingList;
|
|
break;
|
|
case 'offline':
|
|
chat.state.chatService.emit('customer_service_offline', {}, (res) => {
|
|
changeBack(res);
|
|
});
|
|
chat.state.sessionList = chat.state.customerHistoryList;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// 切换回调
|
|
function changeBack(res) {
|
|
if (res.code == 1) chat.state.currentCustomerService = res.data.customer_service;
|
|
callBackNotice(res);
|
|
}
|
|
},
|
|
|
|
// access 接入客户,会触发监听接入,被接入
|
|
socket_customer_access(index) {
|
|
chat.state.chatService.emit(
|
|
'access',
|
|
{ session_id: chat.state.currentCustomer?.session_id },
|
|
(res) => { },
|
|
);
|
|
},
|
|
|
|
// transfer 转接顾客
|
|
socket_transfer(customer_service_id) {
|
|
chat.state.chatService.emit(
|
|
'transfer',
|
|
{
|
|
session_id: chat.state.currentCustomer?.session_id,
|
|
customer_service_id,
|
|
},
|
|
(res) => {
|
|
callBackNotice(res);
|
|
if (res.code == 1) {
|
|
const index = chat.state.customerOnLineList.findIndex(
|
|
(item) => item.session_id == chat.state.currentCustomer?.session_id,
|
|
);
|
|
chat.state.customerOnLineList.splice(index, 1);
|
|
chat.socket_reset();
|
|
}
|
|
},
|
|
);
|
|
},
|
|
}
|
|
|
|
const state = reactive({})
|
|
|
|
// 打开客服
|
|
const showChat = ref(false)
|
|
function onShowChat() {
|
|
showChat.value = !showChat.value;
|
|
|
|
// 初始化聊天
|
|
if (chat.state.connection.status != 'connect') {
|
|
if (!chat.state.connection.isFlag) {
|
|
chat.chatInit();
|
|
}
|
|
chat.open_chat();
|
|
}
|
|
}
|
|
|
|
// 客服是否有未读消息
|
|
const isChatUnreadNum = computed(() => {
|
|
return chat.state.customerOnLineList.reduce((pre, cur) => {
|
|
return pre + cur?.unread_num;
|
|
}, chat.state.customerWaitingList.length);
|
|
})
|
|
|
|
// 修改客服状态
|
|
const customerServiceStatus = {
|
|
online: '在线',
|
|
offline: '离线',
|
|
busy: '忙碌',
|
|
// disconnect: 'sa-duankailianjie',
|
|
}
|
|
function onChangeCustomerServiceStatus(status) {
|
|
if (chat.state.currentCustomerService.status == status) return;
|
|
chat.socket_change_customer_service_status(status);
|
|
};
|
|
|
|
// 切换客服身份
|
|
function onChangeCustomerServiceIdentity(e) {
|
|
chat.socket_change_customer_identity(e);
|
|
};
|
|
|
|
// 修改会话类型
|
|
const sessionTypeList = {
|
|
ing: {
|
|
label: '会话中',
|
|
left: '2px'
|
|
},
|
|
waiting: {
|
|
label: '排队中',
|
|
left: '52px'
|
|
},
|
|
history: {
|
|
label: '历史',
|
|
left: '102px'
|
|
}
|
|
}
|
|
function onChangeSessionType(session, type = '') {
|
|
if (chat.state.sessionType == session) return;
|
|
if (!currentSessionTypeIndexs.hasOwnProperty(session)) {
|
|
currentSessionTypeIndexs[session] = 0;
|
|
}
|
|
chat.socket_change_customer_list({
|
|
session,
|
|
type,
|
|
index: currentSessionTypeIndexs[session],
|
|
});
|
|
}
|
|
|
|
// 操作当前顾客
|
|
const currentSessionTypeIndexs = reactive({});
|
|
function onChangeCurrentSessionTypeIndex(index) {
|
|
if (currentSessionTypeIndexs[chat.state.sessionType] == index) return;
|
|
currentSessionTypeIndexs[chat.state.sessionType] = index;
|
|
chat.socket_change_customer_info(index);
|
|
};
|
|
|
|
// 删除会话中顾客
|
|
function onDeleteSession(session_id, index, sessionType) {
|
|
chat.socket_change_customer(session_id, index, sessionType);
|
|
}
|
|
|
|
// 操作历史顾客
|
|
const historyDeletePopover = reactive({
|
|
flag: {},
|
|
is_del_record: 0,
|
|
});
|
|
function onCancelHistoryDeletePopover(index) {
|
|
historyDeletePopover.flag[index] = false;
|
|
historyDeletePopover.is_del_record = 0;
|
|
}
|
|
function onConfirmHistoryDeletePopover(session_id, index, sessionType) {
|
|
chat.socket_change_customer(
|
|
session_id,
|
|
index,
|
|
sessionType,
|
|
historyDeletePopover.is_del_record,
|
|
);
|
|
onCancelHistoryDeletePopover(index);
|
|
}
|
|
|
|
// 获取除了自己的客服列表
|
|
const avaliableCustomerServicesList = computed(() => {
|
|
return chat.state.customerServicesList.filter((item) => {
|
|
return item.value != chat.state.currentCustomerService.id;
|
|
});
|
|
})
|
|
|
|
// 转接客服
|
|
const transferCustomer = ref(null);
|
|
function onTransferCommand(val) {
|
|
transferCustomer.value = val
|
|
}
|
|
function onTransferCustomer() {
|
|
chat.socket_transfer(transferCustomer.value);
|
|
};
|
|
|
|
// 立即接入
|
|
function onAccessCustomer() {
|
|
chat.socket_customer_access();
|
|
// 获取历史记录
|
|
onChangeSessionType('ing', 'access');
|
|
}
|
|
|
|
// 加载更多
|
|
function onLoadMore() {
|
|
chat.state.historyPagination.page < chat.state.historyPagination.lastPage &&
|
|
chat.socket_customer_history();
|
|
};
|
|
|
|
const showTime = (item, index) => {
|
|
if (chat.state.chatList[index + 1]) {
|
|
let dateString = Moment(chat.state.chatList[index + 1].createtime * 1000).fromNow();
|
|
if (dateString == Moment(item.createtime * 1000).fromNow()) {
|
|
return false;
|
|
} else {
|
|
dateString = Moment(item.createtime * 1000).fromNow();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// 格式化时间
|
|
const formatTime = (time) => {
|
|
let diffTime = Moment().unix() - time;
|
|
if (diffTime > 28 * 24 * 60) {
|
|
return Moment(time * 1000).format('MM/DD HH:mm');
|
|
}
|
|
if (diffTime > 360 * 28 * 24 * 60) {
|
|
return Moment(time * 1000).format('YYYY/MM/DD HH:mm');
|
|
}
|
|
return Moment(time * 1000).fromNow();
|
|
};
|
|
|
|
function replaceEmoji(data) {
|
|
let newData = data;
|
|
if (typeof newData != 'object') {
|
|
let reg = /\[(.+?)\]/g; // [] 中括号
|
|
let zhEmojiName = newData.match(reg);
|
|
if (zhEmojiName) {
|
|
zhEmojiName.forEach((item) => {
|
|
let emojiFile = selEmojiFile(item);
|
|
newData = newData.replace(
|
|
item,
|
|
`<img class="message-emoji" src="${Fast.api.cdnurl(`/assets/addons/shopro/img/chat/emoji/${emojiFile}`)}" />`,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
return newData;
|
|
}
|
|
function selEmojiFile(name) {
|
|
for (let index in emojiList) {
|
|
if (emojiList[index].name == name) {
|
|
return emojiList[index].file;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const messageInput = ref('');
|
|
function getMessageInput(e) {
|
|
if (
|
|
e.target.innerHTML.replace(/ |\s/g, '') &&
|
|
e.target.innerHTML.replace(/ |\s/g, '').indexOf('<br>') != 0
|
|
) {
|
|
messageInput.value = e.target;
|
|
} else {
|
|
messageInput.value = '';
|
|
}
|
|
};
|
|
// 获取焦点
|
|
function getMessageInputFocus() {
|
|
if (window.getSelection) {
|
|
let chatInput = proxy.$refs.messageInputRef;
|
|
chatInput.focus();
|
|
let range = window.getSelection();
|
|
range.selectAllChildren(chatInput);
|
|
range.collapseToEnd();
|
|
} else if (document.selection) {
|
|
let range = document.selection.createRange();
|
|
range.moveToElementText(chatInput);
|
|
range.collapse(false);
|
|
range.select();
|
|
}
|
|
};
|
|
// ctrl + enter 换行 ,enter发送
|
|
function onKeyDown(e) {
|
|
if (e.ctrlKey && e.keyCode == 13) {
|
|
document.execCommand('insertHTML', false, '<br></br>');
|
|
} else if (e.keyCode == 13) {
|
|
// 阻止默认enter
|
|
e.preventDefault();
|
|
onSendMessage();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function onSendMessage() {
|
|
if (!messageInput.value || !chat.state.currentCustomer) return;
|
|
let res = '';
|
|
let elemArr = Array.from(messageInput.value.childNodes);
|
|
elemArr.forEach((child, index) => {
|
|
if (child.nodeName == '#text') {
|
|
res += child.nodeValue;
|
|
if (elemArr[index + 1]?.nodeName == 'IMG' && elemArr[index + 1]?.name != 'emoji') {
|
|
const data = {
|
|
sender_identify: 'customer_service',
|
|
message_type: 'text',
|
|
message: res,
|
|
createtime: Moment().unix(),
|
|
};
|
|
chat.socket_send(data);
|
|
scrollBottom();
|
|
res = '';
|
|
}
|
|
} else if (child.nodeName == 'BR') {
|
|
res += '<br/>';
|
|
} else if (child.nodeName == 'IMG') {
|
|
if (child.name != 'emoji') {
|
|
let srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i;
|
|
let src = child.outerHTML.match(srcReg);
|
|
const data = {
|
|
sender_identify: 'customer_service',
|
|
message_type: 'image',
|
|
message: {
|
|
url: Fast.api.cdnurl(src[1].replace(/http:\/\/[^\/]*/, ''),true),
|
|
path: src[1].replace(/http:\/\/[^\/]*/, ''),
|
|
},
|
|
createtime: Moment().unix(),
|
|
};
|
|
chat.socket_send(data);
|
|
scrollBottom();
|
|
} else {
|
|
res += child.outerHTML;
|
|
}
|
|
} else if (child.nodeName == 'DIV') {
|
|
res += `<div style="width:200px; white-space: nowrap;">${child.outerHTML}</div>`;
|
|
}
|
|
});
|
|
if (res) {
|
|
const data = {
|
|
sender_identify: 'customer_service',
|
|
message_type: 'text',
|
|
message: res,
|
|
createtime: Moment().unix(),
|
|
};
|
|
chat.socket_send(data);
|
|
}
|
|
messageInput.value = '';
|
|
proxy.$refs.messageInputRef.innerHTML = '';
|
|
scrollBottom();
|
|
}
|
|
|
|
function onSelectToolbar(message_type, message) {
|
|
!messageInput.value && getMessageInputFocus();
|
|
let obj
|
|
switch (message_type) {
|
|
case 'emoji':
|
|
let img = `<img src="${Fast.api.cdnurl('/assets/addons/shopro/img/chat/emoji/' + message.file, true)}" name="emoji" style="object-fit: cover;vertical-align:bottom; display: inline-block;width:20px !important;height:20px;margin:2px">`;
|
|
document.execCommand('insertHTML', false, img);
|
|
break;
|
|
case 'text':
|
|
obj = {
|
|
sender_identify: 'customer_service',
|
|
message_type,
|
|
message,
|
|
createtime: Moment().unix(),
|
|
};
|
|
chat.socket_send(obj);
|
|
scrollBottom();
|
|
break;
|
|
case 'image':
|
|
Fast.api.open(`general/attachment/select`, "选择", {
|
|
callback: function (data) {
|
|
obj = {
|
|
sender_identify: 'customer_service',
|
|
message_type,
|
|
message: data.url,
|
|
createtime: Moment().unix(),
|
|
}
|
|
chat.socket_send(obj);
|
|
scrollBottom();
|
|
}
|
|
});
|
|
break;
|
|
case 'goods':
|
|
Fast.api.open(`shopro/goods/goods/select`, "选择商品", {
|
|
callback(data) {
|
|
obj = {
|
|
sender_identify: 'customer_service',
|
|
message_type,
|
|
message: {
|
|
id: data.id,
|
|
title: data.title,
|
|
image: data.image,
|
|
price: data.price,
|
|
stock: data.stock,
|
|
},
|
|
createtime: Moment().unix(),
|
|
};
|
|
chat.socket_send(obj);
|
|
scrollBottom();
|
|
}
|
|
})
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
function onOpenGoodsDetail(id) {
|
|
Fast.api.open(`shopro/goods/goods/add?type=edit&id=${id}`, "商品详情")
|
|
}
|
|
|
|
function onOpenOrderDetail(id) {
|
|
Fast.api.open(`shopro/order/order/detail?id=${id}`, "订单详情")
|
|
}
|
|
|
|
function scrollBottom() {
|
|
chat.state.connection.status == 'connect' &&
|
|
nextTick(() => {
|
|
console.log(proxy.$refs.chatScrollRef, '000')
|
|
console.log(proxy.$refs.chatScrollRef['wrap$'].childNodes[0].offsetHeight, 1)
|
|
console.log(proxy.$refs.chatScrollRef['wrap$'].offsetHeight, 2)
|
|
let scrollTop = proxy.$refs.chatScrollRef['wrap$'].childNodes[0].offsetHeight - proxy.$refs.chatScrollRef['wrap$'].offsetHeight + 20
|
|
console.log(scrollTop, 'scrollTop')
|
|
proxy.$refs.chatScrollRef.setScrollTop(scrollTop);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
// 消息通知
|
|
const showNotification = ref(false)
|
|
function onShowNotification() {
|
|
showNotification.value = true
|
|
chat.state.notificationList = [];
|
|
getNotificationType()
|
|
}
|
|
|
|
// 消息通知-是否有未读数据
|
|
const isNotificationUnreadNum = computed(() => {
|
|
return chat.state.notificationTypeList.reduce((pre, cur) => {
|
|
return pre + cur.unread_num;
|
|
}, 0);
|
|
})
|
|
|
|
// 消息通知-类型
|
|
function getNotificationType() {
|
|
Fast.api.ajax({
|
|
url: 'shopro/notification/notification/notificationType',
|
|
type: 'GET',
|
|
}, function (ret, res) {
|
|
chat.state.notificationTypeList = res.data.notification_type;
|
|
chat.state.notificationType = res.data.notification_type[0].value
|
|
|
|
// 请求列表
|
|
pagination.page = 1;
|
|
getNotificationList()
|
|
return false
|
|
}, function (ret, res) { })
|
|
};
|
|
|
|
// 消息通知-切换类型
|
|
function onChangeNotificationType(type) {
|
|
chat.state.notificationType = type
|
|
chat.state.notificationList = [];
|
|
pagination.page = 1;
|
|
getNotificationList();
|
|
};
|
|
|
|
// 消息通知-列表
|
|
const pagination = reactive({
|
|
page: 0,
|
|
lastPage: 0,
|
|
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
|
|
});
|
|
function getNotificationList() {
|
|
pagination.loadStatus = 'loading';
|
|
Fast.api.ajax({
|
|
url: 'shopro/notification/notification',
|
|
type: 'GET',
|
|
data: {
|
|
search: JSON.stringify({ notification_type: chat.state.notificationType }),
|
|
page: pagination.page,
|
|
},
|
|
}, function (ret, res) {
|
|
if (pagination.page == 1) {
|
|
chat.state.notificationList = []
|
|
}
|
|
res.data.data.forEach((item) => {
|
|
chat.state.notificationList.push(item);
|
|
});
|
|
|
|
pagination.page = res.data.current_page;
|
|
pagination.lastPage = res.data.last_page;
|
|
pagination.loadStatus = pagination.page < pagination.lastPage ? 'loadmore' : 'nomore';
|
|
// 请求之后 把未读改为0
|
|
chat.state.notificationTypeList.map((item) => {
|
|
if (item.value == chat.state.notificationType) {
|
|
item.unread_num = 0;
|
|
}
|
|
});
|
|
|
|
return false
|
|
}, function (ret, res) { })
|
|
};
|
|
function onLoadMoreNotification() {
|
|
if (pagination.page < pagination.lastPage) {
|
|
pagination.page += 1;
|
|
getNotificationList();
|
|
}
|
|
};
|
|
|
|
// 消息通知-标记为已读消息
|
|
function onReadNotification(id, index) {
|
|
Fast.api.ajax({
|
|
url: `shopro/notification/notification/read/id/${id}`,
|
|
type: 'POST',
|
|
}, function (ret, res) {
|
|
chat.state.notificationList[index] = res.data;
|
|
return false
|
|
}, function (ret, res) { })
|
|
}
|
|
|
|
// 消息通知-清空已读消息
|
|
function onClearNotification() {
|
|
Fast.api.ajax({
|
|
url: 'shopro/notification/notification/delete',
|
|
type: 'DELETE',
|
|
}, function (ret, res) {
|
|
pagination.page = 1;
|
|
chat.state.notificationList = []
|
|
getNotificationList();
|
|
return false
|
|
}, function (ret, res) { })
|
|
};
|
|
|
|
return {
|
|
Fast,
|
|
emojiList,
|
|
chat,
|
|
state,
|
|
showChat,
|
|
onShowChat,
|
|
isChatUnreadNum,
|
|
customerServiceStatus,
|
|
onChangeCustomerServiceStatus,
|
|
onChangeCustomerServiceIdentity,
|
|
sessionTypeList,
|
|
onChangeSessionType,
|
|
currentSessionTypeIndexs,
|
|
onChangeCurrentSessionTypeIndex,
|
|
onDeleteSession,
|
|
historyDeletePopover,
|
|
onCancelHistoryDeletePopover,
|
|
onConfirmHistoryDeletePopover,
|
|
avaliableCustomerServicesList,
|
|
transferCustomer,
|
|
onTransferCommand,
|
|
onTransferCustomer,
|
|
onAccessCustomer,
|
|
onLoadMore,
|
|
showTime,
|
|
formatTime,
|
|
replaceEmoji,
|
|
selEmojiFile,
|
|
messageInput,
|
|
getMessageInput,
|
|
getMessageInputFocus,
|
|
onKeyDown,
|
|
onSendMessage,
|
|
onSelectToolbar,
|
|
onOpenGoodsDetail,
|
|
onOpenOrderDetail,
|
|
scrollBottom,
|
|
|
|
showNotification,
|
|
onShowNotification,
|
|
isNotificationUnreadNum,
|
|
onChangeNotificationType,
|
|
pagination,
|
|
onLoadMoreNotification,
|
|
onReadNotification,
|
|
onClearNotification,
|
|
loadingMap: {
|
|
loadmore: {
|
|
title: '查看更多',
|
|
icon: 'el-icon-arrow-left',
|
|
},
|
|
nomore: {
|
|
title: '没有更多了',
|
|
icon: '',
|
|
},
|
|
loading: {
|
|
title: '加载中... ',
|
|
icon: 'el-icon-loading',
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
return SaChat
|
|
})
|
|
|