前言:在写聊天页面时,由于除了文字内容以外,还可以发送图片、视频之类。
而作为用户端聊天页面,有时候不是只有app在使用,浏览器也会打开它。
video标签虽然是作为播放使用,但不同浏览器或多或少对video标签有所改动或影响。
vivo 自带浏览器:只能听声音而无视频,但是有时候又能正常播放(必发)
谷歌浏览器:播放遵从video 自带属性,当video全局播放关闭后,层级将高于body ,导致向下滚动时容器浮动在底部输入框之上。 z-index层级是由video内置决定,此外其他浏览器也各自存在类似此问题。
火狐浏览器:在播放时容易引起黑屏。
UC浏览器:播放完成后,需要两次物理返回才能退出黑屏到正式页,为了避免体验不好,推荐播放时直接新开页面: window.open('视频地址', '_blank') 。(推荐此做法,后面浏览器开播放我干脆就改成用这种在,但是微信/app 这两者打开浏览地址,如果video播放 又会造成一次新开页面,返回时引起 WebSocket重新链接,由于不太清楚里面页面重开的机制,所以此处我使用我原来的逻辑: 列表渲染的 视频图片和播放按钮由后端提供海报,前端提供播放 ,在列表for循环容器外,再造一个video容器 ,此容器只有在点击事件触发时才会展示 (video 自带的层级很高),一般隐藏。再借用 video自带的方法播放,传入点击地址,使容器点击后播放,监听容器变化后 再关闭)
百度浏览器:自带预览图片功能,和vant 组件的预览会产生冲突,故做判断:
var sUserAgent = navigator.userAgent.toLowerCase();
if (sUserAgent.indexOf('baidu') > 0) { //百度自带预览引擎 容易与vant 的插件冲突
state.ImgisShow = false;
}
如果 不是百度浏览器 那么执行
const imgSize = (img: any) => { //预览图片
if (!state.ImgisShow) { // 当百度浏览器开启时,自带预览 这时禁用 vant 插件
console.log('当前为百度浏览器,故关闭vant 预览',)
return
}
state.img_url = img;
document.getElementById("content")!.style.opacity = '0';
document.getElementById("hhh")!.style.opacity = '0';
let newImg = [img];
ImagePreview({
images: newImg,
onClose() {//部分手机app打开页面时(小米) 发现预览图片打开后,内容依旧会展示出来,但是在华为和vivo上没有这种问题 故做隐藏处理【此处 z-index设定不生效!!!】
console.log('预览图片关闭了');
document.getElementById("hhh")!.style.opacity = '1';
document.getElementById("content")!.style.opacity = '1';
},
});
};
完整代码
<script lang="ts" setup>
import {
ref,
reactive,
nextTick,
getCurrentInstance,
Ref,
onBeforeMount,
onMounted,
watch,
computed,
} from "vue";
import Cookies from "js-cookie";
import { Sticky, Toast, ImagePreview } from "vant"; //注册弹窗
import "vant/es/toast/style"; //vant 使用时 ts文件需要样式引入
import showImagePreview from 'vant';
import { newsChatTime } from "./config/time"; //引入时间戳 方法
import service from "./config/request"; //图片上传 接口配置
import { setParams } from "./config/oss"; //上传时的文件名配置
import emoji from "./config/emoji"; //表情包
import { chatsApi } from "./api/index"; //常用接口
import { indexOf, stubString, toNumber } from "lodash";
import { da } from "element-plus/lib/locale";
import { getParamsNew, onScroll } from './config/appBuse';
const wsurl: string = "wss://alphatur-kefu-ws.fangpian-h5.com:8900"; //wss地址
let channelId: any = 1001; //渠道id
let token: any = "";
let userId: any = 0;
let langs: any = "";
let sendmsg: string | undefined = ""; //输入的内容
let content: any = ""; //您好~
let content2: any = ""; //正在为您连接人工客服,请你耐心等待
let content3: any = ""; //人工客服为您服务,请问有什么可以帮您呢?
let _this: any = []; //获取vue实例
const { proxy }: any = getCurrentInstance();
_this = proxy;
let imgList = [
"img1.png",
"img2.png",
"img3.png",
"img4.png",
"img5.png",
"img6.png",
];
let Auatar: string = imgList[Math.round(Math.random() * 6)]; //用户头像 (随机抽取)
Auatar = Auatar != undefined ? Auatar : imgList[0];
console.log('Auatar', Auatar)
let Auatar2: string = '';
const chatApi = chatsApi();
let kefuId: number | string = ""; //客服id
let kefuAuatar: string = "kefu.png"; //客服头像
let heightTemp: Ref<string> = ref(""); //浏览器容器高度
let heightTemp2: Ref<string> = ref(""); //聊天消息列表高度 视口高度- 底部输入框60px
let widthTemp: Ref<string> = ref("");//屏幕宽度
const emojiList = emoji; //客服
const ws = new WebSocket(wsurl); //连接
let touchTop: number = 0;
let startTop: number = 0;
let king = ref('0');//声明一个下拉状态 0为待下拉(下拉查看历史记录消息) 1为加载中 2为加载完成(没有更多了)
var startx, starty;
let inWX = /micromessenger\s*\//i.test(navigator.userAgent);
console.log('inWx', inWX);
let url: any = window.location.href;
let inAPP = url.indexOf('Agen101') == '-1' ? false : true; //存在说明 是app开启的 不存在说明不是app打开的
console.log('inAPP', inAPP)
//定义接口
interface StateInter {
list: any[];
isSend: boolean;
sendLimit: boolean;
showEmoji: boolean;
isShow: boolean;
isFlag: boolean;
isPc: boolean;
defaultPhoneHeight: number; //屏幕默认高度
nowPhoneHeight: number; //屏幕现在的高度
isImg: string;
Lis: boolean;
linshi: any;
video_url: any;
img_url: any;
ImgisShow: boolean,
Isgo: boolean,
}
//全局变量
const state = reactive<StateInter>({
list: [], //内容列表
isSend: false, //是否显示发送按钮
sendLimit: false, //发送状态
showEmoji: false,
isShow: true,
isFlag: false, //用于判断视频是否打开
isPc: false, //用于判断 当前设备打开的是否是Pc端
defaultPhoneHeight: 0, //屏幕默认高度
nowPhoneHeight: 0, //屏幕现在的高度
isImg: "",
Lis: false,//初次进入页面让他下拉 后面以滚动形式来实现
linshi: '',
video_url: '', //视频
img_url: '',//图片
ImgisShow: true, //预览插件是否生效 默认生效
Isgo: true,//由于没做异步,所以 当滚动到顶部 可能造成重复的时间戳传入接口 (网络延迟情况)
});
const checkFull = () => { //判定是开启全屏了 还是关闭全屏了
let isFull = document.fullscreenElement ? true : false;
if (isFull === undefined || isFull === null) isFull = false;
return isFull;
};
//获得角度
function getAngle(angx, angy) {
return Math.atan2(angy, angx) * 180 / Math.PI;
};
//根据起点终点返回方向 1向上 2向下 3向左 4向右 0未滑动
function getDirection(startx, starty, endx, endy) {
var angx = endx - startx;
var angy = endy - starty;
var result = 0;
//如果滑动距离太短
if (Math.abs(angx) < 2 && Math.abs(angy) < 2) {
return result;
}
var angle = getAngle(angx, angy);
if (angle >= -135 && angle <= -45) {
result = 1;
} else if (angle > 45 && angle < 135) {
result = 2;
} else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) {
result = 3;
} else if (angle >= -45 && angle <= 45) {
result = 4;
}
return result;
}
//手指接触屏幕
document.addEventListener("touchstart", function (e) {
startx = e.touches[0].pageX;
starty = e.touches[0].pageY;
}, false);
const getScroll = (event: any) => {
// console.log('event', event)
let scrollBottom = event.target.scrollHeight - event.target.scrollTop - event.target.clientHeight;
console.log(scrollBottom); // 滚动到底部的距离
if (scrollBottom <= 4) {
// document.getElementById("msg")!.innerText += '到底了';
// 判断滚动到底部时
getHistoryMessage(state.list);
}
};
//手指离开屏幕
document.addEventListener("touchend", function (e) {
var endx, endy;
endx = e.changedTouches[0].pageX;
endy = e.changedTouches[0].pageY;
var direction = getDirection(startx, starty, endx, endy);
switch (direction) {
case 0:
// alert("未滑动!");
break;
case 1:
// alert("向上!")
break;
case 2:
// alert("向下!")
console.log('向下滑动')
// getDistance()
// 可视区窗口高度 + 文档滚动高度 - 当前元素与页面顶部距离 - 当前元素高度
// const height = window.innerHeight;
if (state.Lis == false) {
return
}
getHistoryMessage(state.list);
break;
case 3:
// alert("向左!")
break;
case 4:
// alert("向右!")
break;
default:
}
}, false);
// 声明 存在一个设备
const equipment = () => {
console.log("看下当前浏览器设备");
let u = navigator.userAgent;
if (
u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) ||
u.indexOf("Android") > -1 ||
u.indexOf("Linux") > -1
) {
console.log("手机浏览器打开");
(sendmsg = document.getElementById("msg")!.innerText += "手机浏览器打开")
} else if (
navigator.userAgent.match(
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
)
) {
console.log("移动端");
(sendmsg = document.getElementById("msg")!.innerText += "移动端"),
(state.isPc = false);
} else {
console.log("pc端");
(sendmsg = document.getElementById("msg")!.innerText += "pc端"),
(state.isPc = true);
}
};
const setLanguage = (val: string) => {
let params = {
token: token,
language: val,
}
chatApi.setLanguage(params).then((res) => {
console.log('选择语言', res)
})
}
onBeforeMount(() => { //挂载完成前
channelId = getParamsNew("channelId");
token = getParamsNew("token");
userId = getParamsNew("userId");
langs = getParamsNew("lang");
console.log('token', token)
if (langs && langs != undefined) {
window.sessionStorage.setItem("lang", langs);
_this.$i18n.locale = langs;
}
onScroll();//微信禁用 页面下滑
let newLang = sessionStorage.getItem("lang");
if (
newLang === "zh-CN" ||
newLang === "zh-cn" ||
newLang === "zh" ||
newLang === "ZH"
) {
console.log("====zh===");
document.title = "在线客服";
content = "您好~";
content2 = "正在为您连接人工客服,请您耐心等待";
content3 = "人工客服为您服务,请问有什么可以帮您呢?";
setLanguage('ZH')
} else if (newLang === "en-CN" || newLang === "en-us" || newLang === "en") {
console.log("====en===");
document.title = "Online Service";
content = "Hello~";
content2 = "We are connecting to manual customer service for you. Please be patient and wait";
content3 = "Human customer service is at your service. May I help you";
} else {
console.log("====tr====,newLang", newLang);
document.title = "Çevrimiçi Hizmeti";
content = "Merhaba~";
content2 = "Sizin için müşteri hizmetine el bağlantı yapıyoruz. Lütfen sabırlı olun ve bekleyin.";
content3 = "İnsan müşteri hizmeti sizin hizmetinizde. Size yardım edebilir miyim?";
setLanguage('TUR')
}
initWebSocket(); // 初始化websocket
});
// 初始化websocket
const initWebSocket = () => {
ws.onopen = (_status) => {
console.log("socket 连接成功!!");
// 连接建立之后执行send方法发送数据
const actions = {
target: "login",
token: token,
isKefu: "0",
};
console.log("actions ===", actions);
sendMessage(actions);
};
ws.onclose = (status) => {
console.log(status, "用户已掉线,当前对话已关闭");
clearInterval(hearbearTime);
alert(_this.$t('tips12'));
console.log('关闭会话后,点击确定时,刷新页面');
window.location.reload();
};
ws.onmessage = (message) => {
// console.log('收到消息message', message)
onMessage(message); //收到消息
};
ws.onerror = (err) => {
console.log("失败", err);
};
};
//收消息类型
interface OnmessageType {
data: string;
path?: string[];
source: any;
target: any;
timeStamp: number;
type: string;
}
//心跳
let hearbearTime: any;
const hearbeat = () => {
hearbearTime = setInterval(() => {
let HeartbeatData = {
target: "heartbeat",
token: token,
};
sendMessage(HeartbeatData);
}, 3000);
};
//收到消息
const onMessage = (message: OnmessageType) => {
// console.log("收到内容", message);
let data = JSON.parse(message.data);
if (data.code === 0 && data.target == "login") {
hearbeat(); //心跳
let ccc = typeof data.data == "string" ? JSON.parse(data.data) : data.data;
let time = new Date().getTime().toString().slice(0, 10);
if (ccc.avatar) {
Auatar2 = ccc.avatar;
}
connectList({
content, //您好~
content2,//正在为您连接人工客服,请您耐心等候。
contentType: 1,
type: 2,
time,
// msg: _this.$t("text2"),
});
} else if (data.route && data.route == "onChat") {
//接收消息
let ccc = typeof data.data == "string" ? JSON.parse(data.data) : data.data;
if (ccc.contentType == "2" || ccc.contentType == "3") {
// 解密操作
let param2 = {
content: ccc.message, //内容
token: token,
contentType: ccc.contentType, //2图片 3 视频
};
console.log("看下操作解密", param2);
chatApi.Signoss(param2).then((res: any) => {
// 传参成功后,将获取到一个临时 解密的url字段路径,用于临时访问
if (res.code == 0) {
let sendData2 = {
time: new Date().getTime().toString().slice(0, 10),
contentType: ccc.contentType,
message: res.data.url,
type: 2, //用于插入操作 判断当前数据为客服 发出
coverUrl: res.data.coverUrl,
};
console.log("解密成功了没", sendData2);
connectList(sendData2); //本地插入操作
}
});
} else {
connectList(ccc);
}
} else if (data.route && data.route == "makeNewSession") {
let index = '';
state.list.forEach((item, indet) => {
console.log('item', item)
if (item.message && item.message == _this.$t('text2')) {
console.log('里头有通知')
index = '0';
}
if (indet == state.list.length - 1 && index != '0') {
let sendData2 = {
time: new Date().getTime().toString().slice(0, 10),
contentType: 1,//文字
message: _this.$t('text2'),
type: 2, //用于插入操作 判断当前数据为客服 发出
};
state.list.unshift(sendData2);
document.getElementById("lists")!.scrollTop = 0;
}
})
}
};
const renderResize = () => {
console.log('尺寸发生了变化');
heightTemp.value = window.innerHeight + "px";
widthTemp.value = window.innerWidth - 138 + 'px';
let shuru: any = document.getElementById("shuru")?.offsetHeight; //输入框
document.getElementById("app")!.style.height = heightTemp.value;
document.getElementById("KefuBox")!.style.height = heightTemp.value;
document.getElementById("lists")!.style.height = window.innerHeight - shuru + "px"; //消息记录 组件容器高度
// document.getElementById("zhezhao3")!.style.height = heightTemp.value;//遮罩容器
};
onMounted(() => {
renderResize()
window.addEventListener("resize", renderResize, false); //监听页面尺寸 当页面发生翻转后重新重新赋予 容器高度
var sUserAgent = navigator.userAgent.toLowerCase();
if (sUserAgent.indexOf('baidu') > 0) { //百度自带预览引擎 容易与vant 的插件冲突
state.ImgisShow = false;
}
//监听输入的内容,修改div的值和监听动态显示按钮
document.getElementById("msg")?.addEventListener("input", () => {
sendmsg = document.getElementById("msg")?.innerText;
let reg = /^\s*$/g; //正则校验,是否输入为空
let shuru: any = document.getElementById("shuru")?.offsetHeight; //内容高度
console.log('监听输入后,底部容器的高度变化', shuru)
document.getElementById("lists")!.style.height = window.innerHeight - shuru + 'px'; //消息容器
if (
String(sendmsg).length > 0 &&
sendmsg != "" &&
!reg.test(String(sendmsg))
) {
//输入大于0 就不展示上传图片
state.isSend = true;
} else {
state.isSend = false;
}
});
// 挂载完成后 从html 身上获取pause 播放关闭
document.addEventListener("fullscreenchange", () => {
// 监听到屏幕变化,在回调中判断是否已退出全屏
if (!checkFull()) {
state.linshi.pause();//关闭播放
state.linshi.style.display = 'none' //隐藏
document.getElementById("video_T")!.style.opacity = '0';
state.linshi = [];
state.video_url = '';
state.isFlag = false;
} else {
console.log("触发了开启全屏");
state.linshi.play();
document.getElementById("video_T")!.style.opacity = '1';
state.isFlag = true;
}
});
});
// 发送消息内容
interface SendType {
target?: string;
token?: string; // token
message?: string;
channelId?: number | string; //渠道id
userId?: string; //用户id
}
//获取历史消息
// id 根据id 去获取历史内容
const getHistoryMessage = (last: any = "") => {
// 在网络延迟情况下 上一条请求的时间戳与现在时间戳 为同一条(即,接口数据还没返回 而用户进行了 多次重复下拉操作)
// 这时就需要禁止对接口的调用
if (!state.Isgo) {
return
}
king.value = '1'; //加载中
state.Isgo = false;
let time = last
? last[last.length - 1].createTime
: new Date().getTime().toString().slice(0, 10);
let id = last ? last[last.length - 1].id : "";
console.log("time", time);
let param = {
userId: userId,
token: token,
lastTime: time,
lastId: id,
};
chatApi.getMessageRecord(param).then((res: any) => {
if (res.code == 0) {
console.log("历史消息res", res);
let list = res.data.list;
king.value = list.length > 0 ? '0' : '2' // 0下拉查看历史记录 2没有更多了
// let list = [res.data.list[0]];
console.log("list", list);
connectList(list.reverse()); //reverse 顺序变倒叙
}
state.Isgo = true;
});
};
const sendMessage = (message: SendType) => {
message.token = token;
ws.send(JSON.stringify(message));
ws.onmessage = (message) => {
// console.log("发送成功后,接收 target", JSON.stringify(message.target));
onMessage(message); //收到消息
};
};
//视频、图片上传
const afterRead = (file) => {
let type = ""; //存储上传类型
let type2 = "";
if (
file.file.type == "video/mp4" || //.mp4文件
file.file.type == "video/webm" || //.webm文件
file.file.type == "flv" || //.flv文件
file.file.type == "video/x-matroska" || //.mkv文件
file.file.type == "video/quicktime" || //.mov文件
file.file.type == "application/x-shockwave-flash" //.swf文件
) {
type = "3"; //发送视频
type2 = "3"; //oss 传参
} else {
type = "2"; //发送图片
type2 = "2"; //oss 传参
}
// 此时可以自行将文件上传至服务器
service({
// url: "/Extend/Oss/JsConfig?type=1",
url: `/getOSSJsConfig?type=${type2}&token=${token}`,
method: "post",
}).then((res) => {
console.log("图片上传res", res);
if (res.data.code === 0) {
Toast.loading({
duration: 0,
forbidClick: true,
message: _this.$t("text3"), // 视频、图片上传中
className: "transform180",
});
let { formData, fileName } = setParams(file, res.data.data);
service({
url: res.data.data.domain,
method: "post",
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.log("response", response);
console.log("contentType", type);
let sendData = {
time: new Date().getTime().toString().slice(0, 10),
target: "Play/chat",
userId: userId,
token: token,
// contentType: "2",
contentType: type,
channelId: channelId,
message: `${res.data.data.domain}/${fileName}`,
type: 1, //用于插入操作 判断当前数据为用户 发出
};
sendMessage(sendData); //wss发送操作
// 解密操作
let param2 = {
content: `${res.data.data.domain}/${fileName}`, //内容
token: token,
contentType: type2, //2图片 3 视频
};
chatApi.Signoss(param2).then((res: any) => {
// 传参成功后,将获取到一个临时 解密的url字段路径,用于临时访问
if (res.code == 0) {
let sendData2 = {
time: new Date().getTime().toString().slice(0, 10),
userId: userId,
token: token,
contentType: type,
channelId: channelId,
message: res.data.url,
coverUrl: res.data.coverUrl,
type: 1, //用于插入操作 判断当前数据为用户 发出
};
connectList(sendData2); //本地插入操作
}
});
Toast.clear();
})
.catch((err) => {
Toast.clear();
});
}
});
};
watch(state.list, (newL, oldL) => {
// let Fheight = window.innerHeight - 78; //可视区
setTimeout(() => {
let newHeight: any = document.getElementById("list_content")?.offsetHeight; //仅内容(历史记录)
let hhh: any = document.getElementById('hhh')?.offsetHeight;//下拉提示
console.log('hhh 下拉', hhh)
let shuru: any = document.getElementById("shuru")?.offsetHeight; //底部(输入框)
console.log('shuru===========', shuru)
let newHeight2 = window.innerHeight - shuru; //历史记录box父 容器(历史记录)
console.log('newHeight===== 内容高度', newHeight)
console.log('newHeight2=====', newHeight2)
if (newHeight2 <= newHeight + hhh) {
console.log('容器小于内容高度')
state.Lis = false;
console.log('state.Lis', state.Lis)
} else {
console.log('容器大于内容高度')
state.Lis = true;
console.log('state.Lis', state.Lis)
}
}, 500)
})
//清除消息
const clearMsg = () => {
sendmsg = "";
state.isSend = false;
document.getElementById("msg")!.innerText = "";
};
// 拼接list列表
const connectList = (params: any, reverse: boolean = false): void => {
console.log("看下插入params", params);
// state.isSend = false; //展示上传图标
if (Array.isArray(params)) {
params.forEach((e, i) => {
if (params[i - 1] && e.createTime - params[i - 1].createTime > 300) {
e.showTime = true;
}
});
if (state.list.length === 0) {
//第一次获取数据
state.list = params;
} else {
//获取历史数据
if (params.length === 0) {
return;
}
state.list.push(...params);
}
} else {
if (reverse) {
state.list.push(params);
console.log("插入头部", state.list);
} else {
state.list.unshift(params);
console.log("插入底部", state.list);
document.getElementById("lists")!.scrollTop = 0;
}
}
};
// 开启全屏视口播放
const video_play = (item: any) => {
if (!inWX && !inAPP) {//只要不是微信和app打开页面 新开video
window.open(item + '', "_blank");
} else {
state.video_url = item;
let videoId = document.getElementById('video_T');
state.linshi = videoId;
if (videoId?.requestFullscreen) {
console.log('开全屏播放')
videoId.requestFullscreen();
videoId.style.display = 'block';
}
}
};
//发送消息(用于文本类型)
const sendMsg = () => {
if (sendmsg == "") {
Toast(_this.$t("text4")); //请勿发送空内容
return;
}
let sendData = {
time: Number(new Date().getTime().toString().slice(0, 10)),
target: "Play/chat",
channelId: channelId,
userId: userId,
message: sendmsg, //wss 通过此字段发送图片或内容
type: 1, //生成用户端插入时的判断参数
content: sendmsg, //内容
contentType: "1", //内容类型 1为文本 2图片
};
sendMessage(sendData); //发送内容
connectList(sendData); //列表追加内容
clearMsg(); //清除信息 //清除输入框内容
// }
};
//打开表情选项
const openEmoji = () => {
state.showEmoji = !state.showEmoji;
};
//添加表情
const addEmoji = (item, index) => {
state.isSend = true;
openEmoji(); //关闭表情选项
document.getElementById("msg")!.innerText += replace_message(`[em_${index}]`);
sendmsg = document.getElementById("msg")?.innerText;
};
//替换表情
const replace_message = (str) => {
// console.log("看下内容str", str);
str = str.replace(/\[em_([0-9]*)\]/g, (e, i) => {
return emojiList[i];
});
return str;
};
const touchstartFun = (e) => {
console.log("touchstartFun === 点击了屏幕", e);
startTop = e.changedTouches[0].clientY;
};
const touchmoveFun = (e) => {
console.log("touchmoveFun === 触摸了屏幕", e)
// console.log(e.changedTouches[0].clientY)
console.log('state.Lis', state.Lis)
if (state.Lis == false) {
return
}
let top = startTop - e.changedTouches[0].clientY;
let scrollTop = document.getElementById("lists")!.scrollTop;
touchTop = top < -60 ? -60 : top;
if (scrollTop < window.innerHeight - 80) {
nextTick(() => {
document.getElementById("chatBox")!.style.top = touchTop + "px";
});
}
};
const touchendFun = (e) => { //松开后
console.log("touchendFun === ", e);
nextTick(() => {
touchTop = 0;
document.getElementById("chatBox")!.style.top = "0px";
});
};
const imgSize = (img: any) => { //预览图片
if (!state.ImgisShow) { // 当百度浏览器开启时,自带预览 这时禁用 vant 插件
console.log('当前为百度浏览器,故关闭vant 预览',)
return
}
state.img_url = img;
document.getElementById("content")!.style.opacity = '0';
document.getElementById("hhh")!.style.opacity = '0';
let newImg = [img];
ImagePreview({
images: newImg,
onClose() {
console.log('预览图片关闭了');
document.getElementById("hhh")!.style.opacity = '1';
document.getElementById("content")!.style.opacity = '1';
},
});
};
</script>
<template>
<div class="KefuBox" id="KefuBox">
<div class="chilun">
<!-- 历史记录(内容容器) -->
<div class="centent_box" id="centent_box">
<div id="chatBox" class="chat" @touchstart="touchstartFun" @touchmove="touchmoveFun" @touchend="touchendFun">
<div class="chat_content" ref="classifyLayout" id="lists" @click="state.showEmoji = false"
@scroll.passive="getScroll($event)">
<!-- id= content 仅使用于隐藏展示内容-->
<div id="content" class="chat_content_item">
<!-- id="list_content" 仅判断于 内容(不含加载中)容器的高度 是否足够支持滚动 不支持提供触屏下滑效果 -->
<div class="list_content" id="list_content">
<div class="contents" v-for="(item, index) in state.list" :key="index">
<div class="time">
{{
newsChatTime(item.createTime) || newsChatTime(item.time)
}}
</div>
<div class="jus_center" v-if="item.method_name && item.method_name == 'tips'">
<div class="msg_tips">
<img class="msg_tips_image" :src="'.' + kefuAuatar" alt="" v-if="item.type == 2" />
<span class="msg_tips_name">{{ item.msg }}</span>
</div>
</div>
<!-- 我的消息 -->
<div class="message" v-if="item.type === 1">
<img class="message_image" :style="{ 'max-width': widthTemp }" :src="item.content || item.message"
v-if="item.contentType == '2'" @click.stop="imgSize(item.content || item.message)" alt="" />
<div class="videoBox" v-else-if="item.contentType == '3'">
<div class="zz" :style="{ 'width': widthTemp }"></div>
<img :src="item.coverUrl" alt="" class="message_image_other" :style="{ 'max-width': widthTemp }" />
<p class="iconfont icon-bofang" @click="video_play(item.content || item.message)"></p>
</div>
<p class="message_content" v-else>
{{ replace_message(item.content || item.message) }}
</p>
<img class="author" :src="Auatar || '/dist/src' + Auatar" v-real-img="Auatar2" />
</div>
<!-- 客服的消息 -->
<div class="message_other" v-if="!item.method_name && item.type === 2">
<img class="author_other" :src="kefuAuatar || '/dist/src' + kefuAuatar" alt="" srcset="" />
<img class="message_image_other2" :style="{ 'max-width': widthTemp }"
:src="item.content || item.message" v-if="item.contentType == '2'"
@click.stop="imgSize(item.content || item.message)" alt="" />
<div class="videoBox" v-else-if="item.contentType == '3'">
<div class="zz" :style="{ 'width': widthTemp }"></div>
<img :src="item.coverUrl" alt="" class="message_image_other" :style="{ 'max-width': widthTemp }" />
<p class="iconfont icon-bofang" @click="video_play(item.content || item.message)"></p>
</div>
<div class="flex_column message_content_other" v-else-if="item.content2">
<span>
{{ replace_message(item.content || item.message) }}
</span>
<span>{{ item.content2 }}</span>
</div>
<p class="message_content_other" v-else>
{{ replace_message(item.content || item.message) }}
</p>
</div>
</div>
</div>
</div>
<!-- 0 下拉查看历史消息 1加载中 2没有更多了-->
<div class="loading-tips contents" id="hhh">
{{ king == '0' ? _this.$t('text8') : king == '1' ? _this.$t('text10') : _this.$t('text9') }}
</div>
</div>
</div>
<!-- 加载中... -->
<div class="ceshi">{{ _this.$t('text10') }}</div>
</div>
<van-popup v-model:show="state.showEmoji" round :overlay="false" position="bottom" :style="{ height: '40%' }"
style="z-index: 5;">
<div class="emoji_list">
<template v-for="(item, index) in emojiList" :key="index">
<span class="emoji_item" @click="addEmoji(item, index)">{{
item
}}</span>
</template>
</div>
</van-popup>
</div>
<!-- bottom(输入框容器) -->
<div class="bottom_box">
<div class="shuru" id="shuru">
<img class="emoji" src="./assets/emoji.png" alt="" @click="openEmoji" />
<div class="input" contenteditable="true" :placeholder="_this.$t('text5')" id="msg"></div>
<van-uploader :after-read="afterRead" multiple :max-count="1" v-if="!state.isSend">
<img class="add" src="./assets/add.png" alt="" />
</van-uploader>
<!-- 发送 -->
<div class="send" v-else @click="sendMsg">{{ _this.$t("send") }}</div>
</div>
</div>
<!-- 播放容器 -->
<video id="video_T" :src="state.video_url" controls class="topBanner" webkit-playsinline playsinline x5-playsinline
x-webkit-airplay='allow' x5-video-player-type='h5' x5-video-orientation='portraint' x5-video-player-fullscreen=''
style="display: none;">
<!-- <p>当前浏览器与视频类型不兼容,如需播放请移至goole或其他浏览器</p> -->
</video>
<!-- preload="auto" /*这个属性规定页面加载完成后载入视频*/ -->
</div>
</template>
<style scoped>
@import url("./fonts/iconfont.css");
#app {
height: 100vh;
}
/deep/.van-popup--bottom {
bottom: 4rem !important;
}
.van-list__loading {
color: #fff;
}
.van-loading {
transform: rotate(180deg) !important;
text-align: center;
}
.emoji_list {
/* margin-bottom: 4.625rem; */
}
/* 日期 */
.time {
color: #999999;
text-align: center;
font-size: .8125rem;
}
* {
box-sizing: border-box !important;
}
/* 上传中弹窗样式 */
.transform180 {
transform-origin: 25% 50%;
text-align: center;
}
.list_content {
background-color: #f2f2f2;
}
.contents {
transform: rotate(180deg);
}
.flex_column {
flex: 1;
display: flex;
flex-direction: column;
}
/* 总容器盒子 */
.KefuBox {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
position: relative;
}
.chilun {
flex: 1;
display: flex;
overflow: hidden;
z-index: 4 !important;
position: relative;
/* 超出容器的内容将被隐藏 */
}
/* 历史消息容器 */
.centent_box {
flex: 1;
background: #f1f1f1;
transform: rotate(180deg) !important;
overflow-y: auto;
}
.centent_box::-webkit-scrollbar {
width: 0;
}
.message {
display: flex;
justify-content: flex-end;
}
.message_other {
display: flex;
}
.ceshi {
position: fixed;
bottom: 0;
left: 0;
right: 0;
text-align: center;
height: 4.3125rem;
line-height: 4.3125rem;
transform: rotate(180deg);
z-index: -1 !important;
color: #969799;
font-size: 0.9rem;
}
.message_image {
max-height: 200px;
max-width: 200px;
margin: 0.9375rem 0;
border-radius: 0.3125rem;
}
.message_image_other2,
.message_image_other {
max-height: 12.5rem;
border-radius: 0.3125rem;
}
.message_image_other2 {
margin: 0.9375rem 0rem 0.9375rem 0.625rem;
}
.author_other {
width: 2.75rem;
height: 2.75rem;
border-radius: 50%;
}
.message_content_other {
background-color: #ffffff;
border-radius: 0.125rem 0.6875rem 0.6875rem 0.6875rem;
padding: 0.75rem 1.3125rem;
font-size: 0.9375rem;
color: #333333;
margin: 0.9375rem 0rem 0.9375rem 0.625rem;
word-break: break-all;
/* 使中文和英文为一体,一起换行 */
word-wrap: break-word;
margin-right: 3.375rem;
/* 使中文和英文分开换行 */
}
.message_content {
background-color: #c1e6ff;
border-radius: 0.6875rem 0.125rem 0.6875rem 0.6875rem;
padding: 0.75rem 1.3125rem;
font-size: 0.9375rem;
color: #333333;
/* font-weight: bold; */
/* margin-right: 0.625rem; */
word-break: break-all;
/* 使中文和英文为一体,一起换行 */
word-wrap: break-word;
/* 使中文和英文分开换行 */
margin-left: 3.375rem;
}
.author {
width: 2.75rem;
height: 2.75rem;
border-radius: 50%;
margin-left: .625rem;
}
.jus_center {
display: flex;
justify-content: center;
}
.msg_tips {
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff;
width: 16.875rem;
padding: 1.0625rem;
border-radius: 0.8125rem;
margin: 1.25rem 0;
}
.msg_tips_image {
width: 2.75rem;
height: 2.75rem;
border-radius: 50%;
}
.msg_tips_name {
font-size: 0.9375rem;
color: #999999;
margin-left: 0.75rem;
}
.time {
color: #999999;
text-align: center;
}
.chat {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
background-color: #f2f2f2;
}
.videoBox {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin: 0.9375rem 0rem 0.9375rem 0.625rem;
overflow: hidden;
border-radius: 0.3125rem;
}
.videoBox p {
position: absolute;
font-size: 2.3rem;
color: white;
z-index: 2
}
.videoBox .zz {
min-width: 6.25rem;
position: absolute;
background: #000;
opacity: 0.5;
height: 100%;
border-radius: 0.375rem;
}
* {
box-sizing: border-box !important;
}
.loading-tips {
color: #888;
height: 3.125rem;
line-height: 3.125rem;
font-size: .8125rem;
text-align: center;
}
.chat_content {
overflow-y: scroll;
padding: 0 0.9375rem;
display: flex;
flex-direction: column;
}
.chat_content_item {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
/* bottom 底部容器 */
.bottom_box {
width: 100vw;
display: flex;
flex-direction: column;
position: relative;
z-index: 6 !important;
}
.shuru {
padding: 0.9375rem;
box-sizing: border-box;
display: flex;
flex-direction: row;
background: #ffffff;
align-items: center;
z-index: 2;
margin-top: .1rem;
}
.emoji {
width: 2.0625rem;
height: 2.0625rem;
margin-right: 0.9375rem;
}
.add {
width: 2.0625rem;
height: 2.0625rem;
margin-left: 0.9375rem;
margin-top: 0.25rem;
}
.input {
flex: 1;
outline: none;
padding: 0.1875rem;
font-size: 0.75rem;
line-height: 1.5rem;
padding: 0.125rem;
word-wrap: break-word;
overflow-x: hidden;
overflow-y: auto;
border-radius: 1.375rem;
background-color: #f2f2f2;
font-size: 0.9375rem;
color: #999999;
padding: 0.6875rem 0.8125rem;
max-height: 6.25rem;
}
.input:empty::before {
content: attr(placeholder);
}
.send {
width: 4.375rem;
border-radius: 0.25rem;
height: 1.875rem;
text-align: center;
line-height: 1.875rem;
color: #ffffff;
background-color: #1191e7;
margin-left: 0.9375rem;
}
.emoji_item {
text-align: center;
display: inline-block;
width: 12.5%;
height: 3.125rem;
line-height: 3.125rem;
font-size: 1.25rem;
}
</style>