提交 9fd3e21c 编写于 作者: fusheng.liu's avatar fusheng.liu

Merge branch 'develop' into feature/fs

...@@ -15,17 +15,20 @@ ...@@ -15,17 +15,20 @@
<v-footer></v-footer> <v-footer></v-footer>
</div> </div>
</el-container> </el-container>
<chat :showChat="showChat" :currentUser="{}"></chat>
</div> </div>
</template> </template>
<script> <script>
import VHeader from "./views/layout/header.vue"; import VHeader from "./views/layout/header.vue";
import VSlidebar from "./views/layout/slidebar.vue"; import VSlidebar from "./views/layout/slidebar.vue";
import VFooter from "./views/layout/footer.vue"; import VFooter from "./views/layout/footer.vue";
import chat from './components/IM/chat'
import { import {
base64decode, base64decode,
isNotEmptyUtils, isNotEmptyUtils,
getUrlParamsMap, getUrlParamsMap,
ssoLogin ssoLogin,
bindDragHeader
} from "./utils/utils.js"; } from "./utils/utils.js";
import { mapActions, mapGetters } from "vuex"; import { mapActions, mapGetters } from "vuex";
import { getLoginUrl, getInnerLoginUrl } from "./utils/index.js"; import { getLoginUrl, getInnerLoginUrl } from "./utils/index.js";
...@@ -34,7 +37,8 @@ export default { ...@@ -34,7 +37,8 @@ export default {
components: { components: {
VHeader, VHeader,
VSlidebar, VSlidebar,
VFooter VFooter,
chat
}, },
data() { data() {
return { return {
...@@ -42,6 +46,7 @@ export default { ...@@ -42,6 +46,7 @@ export default {
userName: "", userName: "",
authList: [], authList: [],
systemType: 0, systemType: 0,
showChat:false
}; };
}, },
computed: { computed: {
...@@ -51,7 +56,11 @@ export default { ...@@ -51,7 +56,11 @@ export default {
vm = this; vm = this;
vm.getToken(); vm.getToken();
}, },
mounted() {}, mounted() {
setTimeout( function () {
bindDragHeader('.c-header', '.chat-wrap');
}, 1000)
},
methods: { methods: {
// 解密token // 解密token
getToken() { getToken() {
......
.chat-wrap {
position: absolute;
top: 20px;
right: 20px;
//min-height: 900px;
min-width: 700px;
z-index: 1000;
border: 1px solid #8fa4ac;
.component-content {
display: flex;
flex-direction: row;
// min-width: 1200px;
background: none !important;
font-size: 14px;
height: 100%;
.center {
//margin-left: 10%;
//margin-right: 10%;
flex: 1;
background: #fff;
.c-header {
padding: 0 25px;
height: 56px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #f5f5f5;
& > .c-header-l {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
& > img {
display: inline-block;
width: 24px;
height: 24px;
border-radius: 12px;
margin-right: 8px;
}
& > .name {
margin-right: 25px;
color: #333333;
}
& > .time-tip {
font-size: 12px;
color: #999999;
}
}
.close-btn {
width: 100px;
color: #c7c8c9;
cursor: pointer;
}
}
.msg-content {
// width: 100%;
// height: 650px;
word-break: break-all;
overflow: scroll;
display: flex;
flex-direction: column;
margin: 10px 20px;
.msg-item {
display: flex;
flex-direction: row;
margin: 12px 0;
.msg-item-img {
width: 36px;
height: 36px;
margin-right: 12px;
& > img {
width: 100%;
height: 100%;
border-radius: 22px;
}
}
.msg-item-detail {
display: flex;
flex-direction: column;
text-align: left;
font-size: 13px;
& > :first-child {
color: #999999;
margin-bottom: 8px;
}
.send-warpper {
// display: flex;
// flex-direction: row;
// align-items: center;
& > .icon {
float: left;
width: 20px;
height: 20px;
margin-right: 10px;
margin-top: 10px;
}
.mid-text-wrapper {
display: inline-block;
// display: flex;
// flex-direction: row;
// align-items: center;
// & > img {
// width: 18px;
// height: 18px;
// margin-right: 8px;
// cursor: pointer;
// }
}
.mid-text {
padding: 12px 16px;
display: inline-block;
max-width: 520px;
border-radius: 8px;
background: #ebf5fc;
text-align: justify;
color: #333333;
&.no-support {
display: flex;
align-items: center;
& > img {
width: 14px;
height: 14px;
margin-right: 3px;
}
}
}
.link {
color: #2f86f6;
}
& > .mid-pdf {
width: 260px;
display: flex;
flex-direction: row;
justify-content: space-between;
text-align: left;
padding: 10px 15px;
background: #f0f1f2;
border-radius: 8px;
min-height: 80px;
cursor: pointer;
.midp-left {
display: flex;
flex-direction: column;
justify-content: space-around;
margin-right: 10px;
.name {
font-size: 13px;
color: #333333;
word-break: break-word;
}
.size {
font-size: 12px;
color: #999999;
}
}
.midp-icon {
width: 36px;
height: 44px;
& > img {
width: 36px;
height: 100%;
border-radius: 3px;
}
}
}
& > .mid-img {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
// & > .icon {
// width: 20px;
// height: 20px;
// }
// & > .img {
// width: 170px;
// height: 108px;
// border-radius: 8px;
// }
.img-box {
max-width: 192px;
max-height: 320px;
overflow: hidden;
}
}
& > .diagnosis-box {
width: 260px;
text-align: left;
padding: 10px 15px;
background: #f0f1f2;
border-radius: 8px;
min-height: 80px;
.title {
padding-bottom: 10px;
font-size: 14px;
font-weight: 700;
}
.dia-text {
font-size: 12px;
color: #7C7C7C;
.label {
color: #B4B4B4;
}
}
}
& > .time-box {
width: 260px;
text-align: left;
padding: 10px 15px;
background: #f0f1f2;
border-radius: 8px;
min-height: 80px;
.title {
padding-bottom: 10px;
font-size: 14px;
font-weight: 700;
}
.time {
padding-bottom: 10px;
font-size:12px;
font-weight: 700;
}
.time-text {
font-size: 12px;
color: #7C7C7C;
}
}
& > .suggession-box {
width: 260px;
text-align: left;
padding: 10px 15px;
background: #f0f1f2;
border-radius: 8px;
min-height: 80px;
.title {
padding-bottom: 10px;
font-size: 14px;
font-weight: 700;
}
.suggession-text {
font-size: 12px;
color: #7C7C7C;
.label {
color: #B4B4B4;
}
}
}
& > .audio-box audio {
width: 242px;
height: 44px;
background: #EBF5FC;
border-radius: 20px;
}
& > .live-box {
width: 202px;
height: 44px;
line-height: 44px;
background: #f0f1f2;
border-radius: 8px;
text-align: center;
}
& > .diagnosis-end {
width: 100%;
.split-line {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
margin: 15px 0;
font-size: 14px;
color: #999999;
&::before {
flex: 1;
content: '';
height: 1px;
margin-right: 18px;
background: #f0f1f2;
}
&::after {
flex: 1;
content: '';
height: 1px;
margin-left: 18px;
background: #f0f1f2;
}
}
}
}
}
.msg-item-detail.line {
width: 100%;
}
&.cr {
flex-direction: row-reverse;
//justify-content: flex-end;
.msg-item-img {
margin-right: 0;
margin-left: 8px;
}
.msg-item-detail {
text-align: right;
& > .mid-text {
background: #f0f1f2;
}
}
}
}
.split-line {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
margin: 15px 0;
font-size: 14px;
color: #999999;
&::before {
flex: 1;
content: '';
height: 1px;
margin-right: 18px;
background: #f0f1f2;
}
&::after {
flex: 1;
content: '';
height: 1px;
margin-left: 18px;
background: #f0f1f2;
}
}
.error-mg {
display: inline-block;
max-width: 520px;
// height: 24px;
max-width: 520px;
line-height: 16px;
text-align: left;
border-radius: 20px;
opacity: 0.45;
margin: -2px 0 10px;
padding: 4px 12px;
color: #FFFFFF;
background: #000000;
margin-left: 50px;
&.mr {
margin-left: 0;
margin-right: 50px;
}
}
}
.msg-content::-webkit-scrollbar
{
width: 0px;
height: 0px;
background-color: #fff;
}
.c-bottom {
position: relative;
top: -8px;
left: 0;
display: flex;
flex-direction: row;
margin: 16px 12px 16px 25px;
.cb-icon-wrapper {
position: absolute;
top: 0;
right: 70px;
display: flex;
align-items: center;
height: 44px;
img {
width: 20px;
height: 20px;
margin-right: 16px;
cursor: pointer;
}
}
.send-btn {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 60px;
height: 44px;
background: #f3f6f7;
margin-left: 12px;
border-radius: 8px;
cursor: pointer;
& > img {
width: 24px;
height: 24px;
}
&.active {
background: #0d9078;
}
}
}
&.no-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
img {
display: block;
width: 120px;
height: 100px;
}
p {
margin-top: 10px;
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
}
}
}
background: #fff;
.time {
color: #999999;
}
.small {
font-size: 12px;
}
.ld{
-webkit-transition-property: -webkit-transform;
-webkit-transition-duration: 1s;
-moz-transition-property: -moz-transform;
-moz-transition-duration: 1s;
-webkit-animation: rotate 3s linear infinite;
-moz-animation: rotate 3s linear infinite;
-o-animation: rotate 3s linear infinite;
animation: rotate 3s linear infinite;
}
@-webkit-keyframes rotate{from{-webkit-transform: rotate(0deg)}
to{-webkit-transform: rotate(360deg)}
}
@-moz-keyframes rotate{from{-moz-transform: rotate(0deg)}
to{-moz-transform: rotate(359deg)}
}
@-o-keyframes rotate{from{-o-transform: rotate(0deg)}
to{-o-transform: rotate(359deg)}
}
@keyframes rotate{from{transform: rotate(0deg)}
to{transform: rotate(359deg)}
}
}
}
<template>
<div class="chat-wrap" v-show="showChat">
<section class="component-content" id="screenSet">
<article
class="center"
v-loadmore="getOldMSGHistory"
>
<section class="c-header">
<div class="c-header-l">
<img :src="doctorImg" alt />
<span class="name">{{doctorName}}医生的问诊群聊</span>
</div>
</section>
<p class="refreshText"></p>
<diagnosis-Live></diagnosis-Live>
<section id="msgContentId" class="msg-content scroll-box">
<article
v-for="(item, index) in messageList"
:key="index">
<!-- sendOrReceive 为true在右边 -->
<div class="msg-item" :class="{'cr': item.sendOrReceive}">
<div v-if="item.showType != 10" class="msg-item-img">
<img :src="item.avatarImg" alt />
</div>
<div class="msg-item-detail" :class="{'line': item.showType == 10}">
<span v-if="item.showType != 10" class="mid-time">{{item.name}} {{item.timestampStr}}</span>
<div class="send-warpper">
<img
v-if="item.sendOrReceive && item.isShowErrorIcon"
class="icon"
src="../../assets/image/IM/icon-no-send.png"
alt
/>
<img
v-if="item.sendOrReceive && item.isShowLoadingIcon"
class="icon ld"
src="../../assets/image/IM/loading-icon-new.png"
alt
/>
<div v-if="item.showType == 1" class="mid-text-wrapper" style="max-width: 520px;">
<div class="mid-text">{{item.text}}</div>
</div>
<div v-if="item.showType == 2" class="mid-img" v-viewer>
<div class="img-box">
<img
class="img"
:src="item.url"
:style="{width: item.newW + 'px', height: item.newH + 'px' }"
alt
/>
</div>
</div>
<div v-if="item.showType == 3" class="mid-pdf" @click="openPDF(item)">
<div class="midp-left">
<span class="name">{{item.text | shortName(23)}}</span>
<span class="size">{{fileSizeChange(item.size)}}</span>
</div>
<div class="midp-icon">
<img src="../../assets/image/IM/icon-pdf.png" alt />
</div>
</div>
<div v-if="item.showType == 4" class="mid-text">
{{item.text}}
<span class="link">{{item.suffix}}</span>
</div>
<div v-if="item.showType == 5" class="mid-text no-support">
<img src="../../assets/image/IM/icon-warning-circle.png" alt />
<span>该消息类型PC端暂不支持</span>
</div>
<!-- 语音消息 -->
<div v-if="item.showType == 6" class="audio-box">
<audio controls>
<source :src="item.url" type="audio/mpeg">
</audio>
</div>
<!-- 问诊开始与病例模块 -->
<div v-if="item.showType == 7" class="diagnosis-box">
<div class="title">{{item.title}}</div>
<div class="dia-text">
<span class="label">患者:</span>
<span>{{item.text.patientName}} {{item.text.sex == 1 ? '男' : '女'}} {{item.text.age}}</span>
</div>
<div class="dia-text">
<span class="label">病情描述:</span>
<span>{{item.text.illnessDetail}}</span>
</div>
</div>
<!-- 预约时间模块 -->
<div v-if="item.showType == 8" class="time-box">
<div class="title">{{item.title}}</div>
<div class="time">{{item.text.timeStr}}</div>
<div class="time-text">{{item.text.tips}}</div>
</div>
<!-- 音视频与IM的交互 -->
<div v-if="item.showType == 9" class="live-box">
<div class="live-notice">{{item.text}}</div>
</div>
<!-- 问诊结束 -->
<div v-if="item.showType == 10" class="diagnosis-end">
<div class="split-line">{{item.text}}</div>
</div>
<!-- 医生建议模块 -->
<div v-if="item.showType == 11" class="suggession-box">
<div class="title">{{item.title}}</div>
<div class="suggession-text">{{item.text}}</div>
</div>
</div>
</div>
</div>
<span
v-if="item.sendOrReceive && item.isShowErrorMsg"
class="error-mg"
:class="{'mr': item.sendOrReceive}"
>{{item.errorMsg}}</span>
</article>
</section>
<section class="c-bottom">
<el-input type="textarea" placeholder="请输入内容" v-model="sendText" maxlength="499"></el-input>
<div class="cb-icon-wrapper">
<el-upload
class="bg-uploader"
action="#"
accept=".jpg, .png, .pdf"
:show-file-list="false"
:before-upload="beforeUploadFile"
>
<img src="../../assets/image/IM/icon-folder-open.png" alt />
</el-upload>
<img src="../../assets/image/IM/icon-link.png" @click="preSendLinkMsg" alt />
</div>
<div class="send-btn" :class="{'active': canSend}" @click="sendTextMsg">
<img v-show="canSend" src="../../assets/image/IM/send-yes.png" alt />
<img v-show="!canSend" src="../../assets/image/IM/send-no.png" alt />
</div>
</section>
</article>
</section>
<!-- 选择链接弹窗 -->
<el-dialog
title="选择链接"
:show-close="true"
:visible.sync="showSelectDialog"
:close-on-click-modal="false"
width="500px"
class="link-form"
>
<el-form ref="linkFormRef" :rules="rules" :model="linkForm" label-width="100px">
<el-form-item label="普通文本">
<el-col :span="20">
<el-input v-model="linkForm.remark" size="small" maxlength="300"></el-input>
</el-col>
</el-form-item>
<el-form-item label="链接文案" prop="info">
<el-col :span="20">
<el-input v-model="linkForm.info" size="small" maxlength="100"></el-input>
</el-col>
</el-form-item>
<el-form-item label="链接地址" prop="url">
<el-col :span="20">
<el-select
size="small"
style="width: 300px"
clearable
filterable
v-model="linkForm.url"
placeholder="请选择"
>
<el-option
v-for="item in linkList"
:key="item.id"
:label="item.title"
:value="item.id"
></el-option>
</el-select>
</el-col>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer" style="text-align: right;">
<el-button size="small" @click="showSelectDialog = false">取 消</el-button>
<el-button size="small" type="primary" @click="sendLinkMsg">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import BreadCrumb from "@/components/breadcrumb.vue";
import diagnosisLive from "./diagnosis-live";
import { doUpload, getFilePath } from "@/utils/qiniu-util";
import { openLoading, closeLoading, betaHandle, bindDragHeader } from "@/utils/utils";
import { getPicaKFAccid, getPhomeDemain } from "@/utils";
const CONTAINER_HEIGHT = 700;
let forwardMsgIntervalId = null,
continueIntervalId = null,
autoCompletionIntervalId = null,
cacheMap = {};
let _this = null;
export default {
components: {
BreadCrumb,
diagnosisLive
},
props: {
showChat: {
type: Boolean,
default: false,
},
currentUser: {
type: Object,
default: {},
}
},
data() {
return {
curmbFirst: "云鹊客服",
curmbSecond: "当前会话",
sendText: "",
sessionListData: {
currentTimestamp: 0,
myTaskCount: 0,
sessionList: [],
waitingTaskCount: 0
},
currentContinueTimes: 0,
currentSessionIndex: 0, // 当前会话序号
currentSession: {},
currentTaskLogId: "", // 当前会话ID
picakfAccId: "",
historyTimestamp: 0,
realTimestamp: 0,
containerHeight: CONTAINER_HEIGHT,
showSelectDialog: false,
linkForm: {
remark: "",
info: "",
url: ""
},
linkList: [], // 消息列表 showType 1: 文本; 2: 图片; 3: PDF; 4: 链接; 5: 不支持类型; 6: 语音;7: 病例;
messageList: [],
rules: {
info: [
{ required: true, message: "请填写链接显示文案", trigger: "blur" }
],
url: [{ required: true, message: "请选择链接", trigger: "change" }]
},
imgWidth: 0,
imgHeight: 0,
imgProportion: 0,
hasNoHistoryData: false,
tid: '',
teamMemberList: [],
doctorName: '',
doctorImg: ''
};
},
computed: {
canSend() {
return !!this.sendText;
}
},
watch: {
// 监听消息列表的变化,添加sessionFlag(会话结束标志)
messageList: {
handler(newMsgList) {
let l = newMsgList.length;
if (l >= 2) {
for (let i = 1; i < l; i++) {
if (newMsgList[i - 1].taskLogId !== newMsgList[i].taskLogId) {
newMsgList[i].sessionFlag = true;
}
}
}
},
deep: true
},
// 监听到有变化,就缓存一下
sendText(newText) {
cacheMap[this.currentSession.id] = newText || "";
}
},
created() {
_this = this;
this.picakfAccId = getPicaKFAccid();
this.tid = this.$route.query.tid || '3868439091';
this.getFiveContentList();
autoCompletionIntervalId && clearInterval(autoCompletionIntervalId);
autoCompletionIntervalId = setInterval(() => {
this.autoCompletionInterval();
}, 2000);
// 监听键盘的回车按键(回车时发送消息,并阻止其在textarea中的回车换行行为)
document.onkeydown = function(ev) {
var event = ev || event;
if (event.keyCode == 13) {
_this.sendTextMsg();
event.preventDefault();
}
};
this.getMembersList(); // 获取群聊成员信息
},
mounted() {
cacheMap = {};
this.$nextTick(() => {
_this.containerHeight = document.body.clientHeight - 80;
// _this.getElmByID("screenSet").style.height = _this.containerHeight - 76 + "px";
_this.getElmByID("msgContentId").style.height = _this.containerHeight - 211 + "px";
});
},
methods: {
// 含有敏感信息的消息,自行补全提示文案
// 每30秒监测敏感信息
autoCompletionInterval() {
if (!this.messageList.length) return;
// 将带有loading的消息转成失败的
this.messageList.forEach(item => {
if (item.isShowLoadingIcon) {
item.isShowLoadingIcon = false;
item.isShowErrorIcon = true;
}
});
let flag = false, msg;
for (let i = 0; i < this.messageList.length; i++) {
msg = this.messageList[i];
if (msg.isShowErrorIcon && !msg.isShowErrorMsg) {
flag = true;
break;
}
}
if (flag) {
this.getMSGForwardForAC(msg);
}
},
// 查询群组成员列表
getMembersList() {
this.GET("/im/team/member/list", {tid: this.tid}).then(res => {
if (res.code === "000000") {
this.teamMemberList = res.data // 1 问诊医生 2接诊医生 3居民 4其他
this.doctorName = this.teamMemberList[0].name
this.doctorImg = this.teamMemberList[0].avatarImageUrl
this.getMSGHistory() // 查询群聊历史消息
}
});
},
// 查询医生和居民的消息 - 向前查找
getMSGForwardForAC(msg) {
let params = {
includeFlag: 0,
lastMsgTimestamp: msg.realTimestamp,
limit: 50,
tid: this.tid
};
this.POST("/im/team/message/forward", params).then(res => {
if (res.code === "000000") {
this.contactForwardMessage(res.data, false);
}
});
},
// 获取元素
getElmByID(elmId) {
return document.getElementById(elmId);
},
// 查询医生和居民的消息历史(下拉刷新时调用)
getOldMSGHistory() {
if(this.hasNoHistoryData) return
let params = {
includeFlag: 0, // 不带本条消息
lastMsgTimestamp: this.historyTimestamp,
limit: 20,
tid: this.tid
}
this.POST("/im/team/message/history", params).then(res => {
if (res.code === "000000") {
// 将新消息合并到之前的消息中, 并且重置最后一条消息
if(res.data && res.data.length > 0) {
this.convertMessageList(res.data, 3)
} else {
this.hasNoHistoryData = true
}
} else {
this.$message({
message: res.message,
type: "error"
});
}
});
},
// 查询医生和居民的消息历史(第一次进来时就调用)
getMSGHistory(session) {
let params = {
includeFlag: 1, // 带本条消息
limit: 20,
tid: this.tid
};
this.POST("/im/team/message/history", params).then(res => {
if (res.code === "000000") {
this.convertMessageList(res.data, 1)
} else {
this.$message({
message: res.message,
type: "error"
});
}
});
},
// 查询医生和居民的消息 - 向前查找
// 将获取消息列表插入到当前消息列表的最后
getMSGForward() {
let params = {
includeFlag: 0,
lastMsgTimestamp: this.realTimestamp,
limit: 20,
tid: this.tid
};
this.POST("/im/team/message/forward", params).then(res => {
if (res.code === "000000") {
this.convertMessageList(res.data, 2)
} else {
this.$message({
message: res.message,
type: "error"
});
}
});
},
/* 转换消息格式
处理的消息类型只有5种:
type == TEXT(showType:1)
type == image 或 picture(showType:2)
type == custom时,18: PDF文件(showType:3); 19: 链接信息(showType:4); 1 ~ 17: 不支持的消息类型(showType:5);
directFlag 1: 第一次取数据; 2: 拼接实时消息(push); 3: 拼接历史消息(unshift);
*/
convertMessageList(messageList, directFlag = 1) {
messageList.sort((a, b) => {
return a.timestamp - b.timestamp;
})
let msg = null,
content = null,
text = "",
suffix = "",
showType = 1,
size = 0,
url = "",
title = "",
cMessageList = [];
messageList.forEach((rawMsg, index) => {
text = "";
suffix = "";
showType = 1;
size = 0;
url = "";
title = "";
msg = Object.assign({}, rawMsg);
msg.sendOrReceive = rawMsg.fromAccId === this.picakfAccId; // 判断消息是显示在左边还是右边, true 右
//msg.avatarImg = msg.sendOrReceive ? this.kfAvatar : this.currentSession.avatarImageUrl;
for(let i = 0; i < this.teamMemberList.length; i++) {
if(rawMsg.fromAccId == this.teamMemberList[i].accId) {
msg.avatarImg = this.teamMemberList[i].avatarImageUrl;
msg.name = this.teamMemberList[i].name;
break
}
}
if (msg.type.toLowerCase() == "custom") {
content = JSON.parse(msg.content)
text = content.content
if (content.bizType == -1) { // 系统消息:消息由于违规未发送成功(可以不处理)
showType = -1
text = content.content
} else if (content.bizType == 18) { // PDF
showType = 3
text = content.name
size = content.size
url = content.url
} else if (content.bizType == 19) { // 链接
showType = 4
text = content.content
suffix = content.suffix
} else if(content.bizType == 22){ // 病例
showType = 7
title = content.title
text = JSON.parse(content.content)
} else if(content.bizType == 23) { // 预约时间
showType = 8
title = content.title
text = JSON.parse(content.content)
} else if(content.bizType == 24){ // 音视频与IM交互
showType = 9
text = content.content
} else if(content.bizType == 25){ // 本次问诊结束
showType = 10
text = content.content
} else if(content.bizType == 26){ // 医生建议
showType = 11
title = content.title
text = content.content
}else {
showType = 5
}
} else if( msg.type.toLowerCase() == "image" || msg.type.toLowerCase() == "picture") { // 图片
content = JSON.parse(msg.content);
url = content.url;
text = content.name;
showType = 2;
this.imgSizeHandleNew(msg, content.w, content.h);
}else if(msg.type.toLowerCase() == "audio") { // 语音
content = JSON.parse(msg.content)
url = content.url
showType = 6
} else {
showType = 1 // 文本
text = msg.content
}
msg.title = title
msg.text = text
msg.showType = showType
msg.suffix = suffix
msg.size = size
msg.url = url
msg.sessionFlag = false
cMessageList.push(msg)
});
if (directFlag === 1) {
this.messageList = []
this.$forceUpdate()
forwardMsgIntervalId && clearInterval(forwardMsgIntervalId)
this.messageList = cMessageList
this.$forceUpdate()
setTimeout(() => {
this.$nextTick(() => {
const scrollBoxDom = document.querySelector(".scroll-box")
scrollBoxDom.scrollTop = scrollBoxDom.scrollHeight
});
}, 100);
this.currentContinueTimes = this.sessionListData.currentTimestamp - this.currentSession.handleStartTime
continueIntervalId && clearInterval(continueIntervalId)
continueIntervalId = setInterval(() => {
this.currentContinueTimes += 1000
}, 1000);
// 最新消息,要合并CUSTOM类型中,bizType是-1的数据(系统消息)
} else if (directFlag === 2) {
this.contactForwardMessage(cMessageList)
} else {
if (cMessageList.length) {
this.messageList.unshift(...cMessageList)
this.$nextTick(() => {
const scrollBoxDom = document.querySelector(".scroll-box")
scrollBoxDom.scrollTop = 1000
});
}
}
// 重新设置历史与实时的时间戳
if (this.messageList.length) {
this.historyTimestamp = this.messageList[0].timestamp
let timestamp = this.messageList[this.messageList.length - 1].timestamp
if (timestamp) {
this.realTimestamp = timestamp
}
}
// 自己发送消息时,暂时不刷新消息
if (directFlag == 1) {
forwardMsgIntervalId = setInterval(() => {
this.getMSGForward()
}, 3000)
}
this.$forceUpdate()
},
// 接接数据
contactForwardMessage(cMessageList, canPush = true) {
let content = {},
signature = "",
msgIndex = -1,
newMsgList = [],
flag = false;
cMessageList.forEach(item => {
content = {};
signature = "";
msgIndex = -1;
newMsgList = [];
if (item.type.toLowerCase() == "custom") {
content = JSON.parse(item.content);
if (content.bizType == -1) {
signature = content.signature;
msgIndex = this.messageList.findIndex(m => {
return m.signature == signature && !m.isShowErrorMsg;
});
if (msgIndex > -1) {
flag = true;
this.messageList[msgIndex].errorMsg = content.content;
this.messageList[msgIndex].isShowErrorMsg = true;
}
this.$forceUpdate();
} else {
flag = true;
canPush && this.messageList.push(item);
}
} else {
flag = true;
canPush && this.messageList.push(item);
}
});
if (flag) {
this.$nextTick(() => {
var element = document.querySelector(".scroll-box");
if (element.scrollTop >= element.scrollHeight - element.offsetHeight - 400) {
element.scrollTop = element.scrollHeight - element.offsetHeight;
}
});
}
},
// 打开PDF
openPDF(item) {
window.open(item.url, "__blank");
},
// 根据字段名及其值,从数组中查找对象
findItemByKeyAndVal(arr, key, value, flag = 0) {
if (flag == 0) {
return arr.find(item => {
return item[key] == value;
});
} else if (flag == 1) {
return arr.findIndex(item => {
return item[key] == value;
});
}
},
// 获取积木列表
getFiveContentList() {
this.GET(
"/contents/admin/template/queryTemplate?publishFlag=5&pageNo=1&pageSize=99999"
).then(res => {
if (res.code === "000000") {
this.linkList = res.data.templateList || [];
} else {
this.$message({
message: res.message,
type: "error"
});
}
});
},
// 上传文件
beforeUploadFile(file) {
console.log("file", file);
let fileSize = file.size / (1024 * 1024);
if (fileSize > 5) {
this.$message({
message: "请上传小于5M的文件",
type: "warning"
});
return;
}
openLoading(_this);
doUpload(
_this,
file,
getFilePath(file, null),
"preview4",
"progress",
""
).then(function(resData) {
closeLoading(_this);
let params = {};
params.fileSize = resData.size;
params.fileExt = resData.ext;
params.url = resData.fullPath;
params.info = resData.name;
params.type = 2;
// 如果是图片,则要获取其宽与高
if (
params.fileExt.toLowerCase() === ".jpg" ||
params.fileExt.toLowerCase() === ".jpeg" ||
params.fileExt.toLowerCase() === ".png"
) {
params.type = 1;
let image = new Image();
image.src = params.url;
image.onload = function() {
let _img = this;
params.width = _img.width;
params.height = _img.height;
_this.sendCommonMsg(params);
};
} else {
_this.sendCommonMsg(params);
}
});
},
// 打开发送链接弹框
preSendLinkMsg() {
this.linkForm = {
remark: "",
info: "",
url: ""
};
this.showSelectDialog = true;
},
// 发送带链接消息
sendLinkMsg() {
this.$refs["linkFormRef"].validate(valid => {
if (valid) {
let params = Object.assign({}, this.linkForm);
params.url = getPhomeDemain() + `/template_v2/?id=${params.url}`;
params.type = 3;
this.sendCommonMsg(params);
this.showSelectDialog = false;
}
});
},
// 发送文本消息
sendTextMsg() {
if (!this.canSend) return;
this.sendCommonMsg({ info: this.sendText });
this.sendText = "";
},
/* 处理发送消息
1: 先将消息体直接显示在对话框中
2: 设置一个时间戳,以便再次找回
3: 保存再次发送的数据
4: 设置各种状态(1:isShowErrorIcon; 2:isShowLoadingIcon; 3:isShowErrorMsg)
*/
handleSendMsg(params, sendId) {
let text = "";
let msg = Object.assign({}, params);
msg.fromAccount = this.tid;
msg.toAccount = this.picakfAccId;
// type: 0, // 类型 0文本 1图片 2pdf 3链接
msg.text = params.info || "";
msg.suffix = params.remark || "";
if (msg.type == 3) {
msg.suffix = params.info;
msg.text = params.remark || "";
}
msg.size = params.fileSize;
msg.url = params.url;
msg.showType = params.type - 0 + 1;
msg.sessionFlag = false;
msg.isShowLoadingIcon = true;
msg.isShowErrorIcon = false;
msg.isShowErrorMsg = false; // 只有在下次拉取新数据时才有可能是为ture
msg.extData = Object.assign({}, params); // 再将发送时的数据
msg.sendId = sendId;
msg.sendOrReceive = true;
msg.timestampStr = new Date().format("hh:mm");
msg.realTimestamp = this.realTimestamp;
msg.taskLogId = this.currentTaskLogId;
this.teamMemberList.forEach(item => {
if(this.picakfAccId = item.accId) {
msg.avatarImg = item.avatarImageUrl // 运营头像
}
})
if(msg.type == 1) {
this.imgSizeHandleNew(msg, msg.width, msg.height)
}
this.messageList.push(msg)
this.$nextTick(() => {
var element = document.querySelector(".scroll-box")
element.scrollTop = element.scrollHeight
});
},
// 发送通用消息
async sendCommonMsg(params) {
let sendMsgParams = {
fromAccount: this.picakfAccId,
toAccount: this.tid,
fileExt: "", // 文件扩展名称图片或PDF文件)
fileSize: 0, // 文件大小(图片或PDF文件)
height: 0, // 图片高度(仅图片)
width: 0, // 图片宽度(仅图片)
info: "", // 文本内容,图片的名称,pdf的名称,链接显示内容
md5: "", // 图片或文件MD5 暂时由后台生成
remark: "", // 其他信息(链接中的前缀文案)
type: 0, // 类型 0文本 1图片 2pdf 3链接
url: "" // url地址(图片、pdf,链接)
}
params = Object.assign(sendMsgParams, params)
// 将获取新数据的定时器关闭
forwardMsgIntervalId && clearInterval(forwardMsgIntervalId)
let sendId = new Date().getTime()
this.handleSendMsg(params, sendId)
await this.POST("/im/team/op/message/send", params)
.then(res => {
if (res.code === "000000") {
// 校验结果:1校验通过 2校验不通过
let msg = this.messageList[this.messageList.length - 1];
if (msg.sendId !== sendId) {
msg = this.getMsgBySendId(sendId);
}
if (res.data.checkFlag == 1) {
this.realTimestamp = res.data.timetag
msg.isShowLoadingIcon = false
msg.isShowErrorIcon = false
msg.isShowErrorMsg = false // 只有在下次拉取新数据时才有可能是为ture
} else {
msg.signature = res.data.signature
msg.isShowLoadingIcon = false
msg.isShowErrorIcon = true
msg.isShowErrorMsg = false // 只有在下次拉取新数据时才有可能是为ture
}
this.teamMemberList.forEach((item, index) => {
if(params.fromAccount == item.accId) {
msg.name = item.name
}
})
} else {
this.$message({
message: res.message,
type: "error"
});
}
})
.catch(error => {
let msg = this.messageList[this.messageList.length - 1]
if (msg.sendId !== sendId) {
msg = this.getMsgBySendId(sendId)
}
msg.isShowLoadingIcon = false
msg.isShowErrorIcon = true
msg.isShowErrorMsg = false // 只有在下次拉取新数据时才有可能是为ture
msg.canRepeatSend = true
});
this.$forceUpdate()
// 重新开启定时器
forwardMsgIntervalId = setInterval(() => {
this.getMSGForward()
}, 3000)
},
// 根据sendId,查找到对应的消息
getMsgBySendId(sendId) {
console.log("------------getMsgBySendId------------");
let l = this.messageList.length,
i = l - 1;
for (; i > 0; i--) {
if (this.messageList[i].sendId == sendId) {
break;
}
}
return this.messageList[i];
},
// 文件大小单位转换
fileSizeChange(val) {
return betaHandle(val);
},
},
beforeDestroy() {
forwardMsgIntervalId && clearInterval(forwardMsgIntervalId);
autoCompletionIntervalId && clearInterval(autoCompletionIntervalId);
}
};
</script>
<style lang="scss" scoped>
@import "./chat.scss";
</style>
<template>
<div class="livebox">
<div class="top">
<div class="top-left">
<h1 class="title">{{ title }}</h1>
<div class="time-message">
<p>设定时长: {{ time }}分钟</p>
<p v-if="startTime && endTime">总时长: {{ useTime }}</p>
<p v-if="startTime && endTime">剩余时长: {{ loseTime }}</p>
</div>
</div>
<div class="top-right">
<img
:src="isMicOn ? voiceSmallImg : voiceCloseImg"
alt=""
srcset=""
class="icon"
@click="taggleM"
/>
<div class="close" @click="overFn">结束会话</div>
</div>
</div>
<div class="main">
<div
class="viedo-wrapper"
v-for="(item, index) of memberList"
:key="index"
>
<div class="text">
<p>{{ item.name }}</p>
<p>{{ item.role == 1 ? "问诊医生" : "接诊医生" }}:</p>
</div>
<div class="viedo">
<div
:id="rtc.viewslist[index].id"
v-if="rtc && rtc.viewslist[index]"
></div>
<!-- 用户声音icon -->
<img
:src="
rtc && rtc.viewslist[index] && rtc.viewslist[index].vioce
? voiceSmallImg
: voiceCloseImg
"
alt=""
srcset=""
class="user-icon"
v-if="item.status == 2"
/>
<img
:src="setImg(item.status)"
alt=""
srcset=""
class="video-icon"
v-else
/>
<div
class="mask"
v-if="rtc && rtc.viewslist[index] && rtc.viewslist[index].mask"
>
<img
:src="type == 1 ? voiceImg : noCameraImg"
alt=""
:class="{ vocie: type == 1 }"
/>
</div>
</div>
<div class="time">
<!-- //等待连接还是已经下麦 1为等待 2为进行中 3为下麦 -->
<div class="time-content">
<el-button
class="call-btn"
size="mini"
:disabled="item.status == 2 && item.timeleft == 0"
type="primary"
:loading="item.timeleft > 0"
@click="drivingCall(item)"
>
<span v-if="item.timeleft == 0"
>呼叫{{ item.role == 1 ? "问诊医生" : "接诊医生" }}</span
>
<time-left
@setItem="setItem($event, item)"
v-else
:timeleft="item.timeleft"
></time-left>
</el-button>
<span>
{{ showText(item.status, item.role, item) }}
</span>
</div>
</div>
</div>
</div>
<div class="out" @click="leave">退出</div>
<alert ref="alert"></alert>
</div>
</template>
<script>
import {
getLiveTimeText,
countDown,
getBroswer,
laseTime,
lastm,
} from "@/utils/live";
import RtcClient from "../../utils/RtcClient.js";
import { openLoading, closeLoading } from "@/utils/utils";
import alert from "@/components/common/alert.vue";
import timeLeft from "@/components/timeLeft";
export default {
components: {
alert,
timeLeft,
},
data() {
return {
closeCallImg: require("../../assets/image/live/close-call.png"),
voiceCloseImg: require("../../assets/image/live/voice-close.png"),
voiceSmallImg: require("../../assets/image/live/voice-small.png"),
voiceImg: require("../../assets/image/live/voice.png"),
waitingCallImg: require("../../assets/image/live/waiting-call.png"),
noCameraImg: require("../../assets/image/live/no-camera.png"),
roleAnchor: "anchor", // 主播
rtc: null,
roomId: "",
sdkAppId: "",
userSig: "",
userId: "",
viedoParams: null,
isMicOn: false,
askTime: 0, // 问诊医生接入时长
answerTime: 0, // 接诊医生接入时长
useTime: 0, // 问诊时长
loseTime: 0, // 问诊剩余时长
askTimeFn: null,
answerTimeFn: null,
useTimeFn: null,
loseTimeFn: null,
type: 2, // 1: 语音 2: 视频
startTime: 0,
endTime: 0,
time: 30, //总时长
memberList: [],
tid: "", // 群id
diagnoseLogId: "", //问诊id
};
},
created() {
this.tid = this.$route.query.tid || "3868439091";
this.diagnoseLogId = this.$route.query.diagnoseLogId || "38";
// openLoading(this);
this.init();
},
computed: {
title() {
return this.type == 1 ? "音频问诊" : "视频问诊";
},
},
methods: {
init() {
if (this.checkChrome()) {
this.getViedoParams();
this.getInfo();
} else {
this.$nextTick(() => {
this.$refs.alert
.init({
confirmTxt: "我知道了",
title: `请下载新版Chrome浏览器`,
})
.then(() => {
closeLoading(this);
this.$router.go(-1);
})
.catch((err) => {
closeLoading(this);
this.$router.go(-1);
});
});
}
},
// 设置item
setItem(data, item) {
item.timeleft = 0;
},
// 主动呼叫
drivingCall(data) {
// /team/call/direct/{imAccId}呼叫
let url = `/im/team/call/direct/`;
let params = {
imAccId: data.accId,
};
this.POST(url, params).then((res) => {
if (res.code === "000000") {
// this.tid = this.$route.query.tid || "";
// this.diagnoseLogId = this.$route.query.diagnoseLogId || "";
this.getInfo(data);
} else {
this.$message({
message: res.message,
type: "warning",
});
}
});
},
// 获取相关信息
getInfo(data) {
let role = data ? data.role : "";
let url = `/im/team/detail?tid=${this.tid}`;
this.GET(url)
.then((res) => {
if (res.code == "000000") {
let { liveInfo, memberList } = res.data;
if (liveInfo) {
this.startTime = liveInfo.startTimestamp;
this.endTime = liveInfo.endTimestamp;
this.time = lastm(this.startTime, this.endTime);
this.roomId = Number(liveInfo.roomId);
this.type = liveInfo.liveType;
}
if (memberList && memberList.length) {
memberList.forEach((item) => {
if (role) {
if (item.role == 1) {
if (role == 1) {
this.memberList[0] = Object.assign(item, {
status: 1,
timeleft: 60,
});
}
//等待连接还是已经下麦 1为等待 2为进行中 3为下麦
}
if (item.role == 2) {
if (role == 2) {
this.memberList[1] = Object.assign(item, {
status: 1,
timeleft: 60,
});
}
}
} else {
if (item.role == 1) {
this.memberList[0] = Object.assign(item, {
status: 1,
timeleft: 0,
});
//等待连接还是已经下麦 1为等待 2为进行中 3为下麦
}
if (item.role == 2) {
this.memberList[1] = Object.assign(item, {
status: 1,
timeleft: 0,
});
}
}
//在当前群组中的角色 1: 问诊医生 2: 接诊医生 3: 居民 4: 其他
if (item.role == 4) {
this.userId = item.liveUserId;
}
});
}
this.getAppId();
} else {
this.getErr();
}
})
.catch(() => {
this.getErr();
});
},
// 获取AppId
getAppId() {
let req = {};
this.GET("/coupler/app/trtc/sdkappid", req)
.then((res) => {
if (res.code == "000000") {
this.sdkAppId = res.data.sdkAppId;
this.getSing();
} else if (res.code == "200006" || res.code == "200000") {
if (this.rtc) {
this.leave();
}
this.getErr();
}
})
.catch((err) => {
this.getErr();
});
},
// 获取签名
getSing() {
let req = {
sdkAppId: this.sdkAppId,
userId: this.userId,
};
this.POST("/coupler/usersig/trtc", req)
.then((res) => {
if (res.code == "000000") {
this.userSig = res.data.userSig;
this.clientLogin();
} else if (res.code == "200006" || res.code == "200000") {
if (this.rtc) {
this.leave();
}
this.getErr();
}
})
.catch((err) => {
this.getErr();
});
},
// 创建trtcClient
clientLogin() {
let obj = {
roomId: this.roomId,
role: this.roleAnchor,
sdkAppId: this.sdkAppId,
userId: this.userId,
userSig: this.userSig,
vueInstance: this,
};
console.log('--=-123123-', obj, RtcClient );
this.rtc = new RtcClient(obj);
this.$nextTick(() => {
this.$refs.alert
.init({
confirmTxt: "我知道了",
title: `为了更好的体验,请保证您输出设备的正常使用`,
})
.then(() => {
Promise.all([this.rtc.join()]).then((res) => {
this.ispending();
let t = setTimeout(() => {
closeLoading(this);
this.muteLocalAudio();
clearTimeout(t);
}, 1000);
});
})
.catch((err) => {
Promise.all([this.rtc.join()]).then((res) => {
this.ispending();
let t = setTimeout(() => {
closeLoading(this);
this.muteLocalAudio();
clearTimeout(t);
}, 1000);
});
});
});
},
// 获取视频参数
getViedoParams() {
this.GET("/coupler/app/config/push/stream/params")
.then((res) => {
if (res.code == "000000") {
this.viedoParams = res.data;
} else if (res.code == "200006" || res.code == "200000") {
closeLoading(this);
if (this.rtc) {
this.leave();
}
}
})
.catch((err) => {
console.log("获取视频参数数据失败");
});
},
// 切换话筒
taggleM() {
if (this.isMicOn) {
this.muteLocalAudio();
} else {
this.unmuteLocalAudio();
}
},
// 关闭mc
muteLocalAudio() {
this.isMicOn = false;
this.rtc.muteLocalAudio();
},
// 打开mc
unmuteLocalAudio() {
this.isMicOn = true;
this.rtc.unmuteLocalAudio();
},
// 显示文案
showText(status, role, item) {
// 1 呼叫中 2 接入 3离线
if (item.status == 2) {
item.timeleft = 0;
}
let text = "";
switch (status) {
case 1:
text = "呼叫中";
break;
case 3:
text = "已离线";
break;
default:
text = `已接入: ${role == 1 ? this.askTime : this.answerTime}`;
break;
}
return text;
},
//设置图像 1为等待 2为进行中 3为下麦
setImg(status) {
let img = null;
switch (status) {
case 1:
img = this.waitingCallImg;
break;
case 3:
img = this.closeCallImg;
break;
default:
img = this.closeCallImg;
break;
}
return img;
},
// 用户上线
addNewMember(id) {
this.memberList.forEach((item) => {
if (item.liveUserId == id) {
item.status = 2;
this.setTime(item.role);
}
});
},
// 用户下线
removeMember(id) {
this.memberList.forEach((item) => {
if (item.liveUserId == id) {
item.status = 3;
}
});
},
//设置进行时长 1表示为问诊 2为接诊 3为问诊开始 4为问诊结束
setTime(flag) {
let text = "";
switch (flag) {
case 1:
text = "askTime";
break;
case 2:
text = "answerTime";
break;
case 3:
text = "useTime";
break;
case 4:
text = "loseTime";
break;
default:
break;
}
let t = 0;
let liveTime = Number(sessionStorage.getItem(`TIME_${text}`) || t);
this[`${text}Fn`] = setInterval(() => {
liveTime += 1000;
this[text] = getLiveTimeText(liveTime, text);
}, 1000);
},
// 问诊是否进行中
ispending() {
if (this.startTime < new Date() < this.endTime) {
this.setTime(3);
this.loseTimeFn = setInterval(() => {
this.loseTime = countDown(this.endTime);
let timeObj = laseTime(this.endTime);
if (
(timeObj.leftm == 5 || timeObj.leftm == 3 || timeObj.leftm == 1) &&
timeObj.lefts == 0 &&
timeObj.lefth == 0
) {
this.$nextTick(() => {
this.$refs.alert
.init({
confirmTxt: "我知道了",
title: `距离会诊结束还剩不足${timeObj.leftm}分钟`,
})
.then(() => {})
.catch((err) => {});
});
}
}, 1000);
} else if (new Date() < this.startTime) {
let t = setInterval(() => {
if (new Date() > this.startTime) {
clearInterval(t);
this.ispending();
}
}, 1000);
}
},
// 检查是否为chrome
checkChrome() {
return getBroswer().broswer == "Chrome";
},
// client离开房间
leave() {
this.rtc.leave();
this.clearSession();
window.location.href = "about:blank";
window.close();
},
// 结束会话
overFn() {
this.$refs.alert
.init({
cancleTxt: "取消",
confirmTxt: "我知道了",
title: `确定要结束会话么?`,
})
.then(() => {
this.out();
})
.catch((err) => {});
},
out() {
let url = `/diagnose/admin/diagnose/endCall/${this.diagnoseLogId}`;
let params = {};
this.POST(url, params)
.then((res) => {
if (res.code == "000000") {
this.leave();
} else {
this.$refs.alert
.init({
confirmTxt: "我知道了",
title: `操作失败,请稍后重试`,
})
.then(() => {})
.catch((err) => {});
}
})
.catch(() => {
this.$refs.alert
.init({
confirmTxt: "我知道了",
title: `操作失败,请稍后重试`,
})
.then(() => {})
.catch((err) => {});
});
},
// 获取信息失败
getErr() {
closeLoading(this);
// this.$nextTick(() => {
// this.$refs.alert
// .init({
// confirmTxt: "我知道了",
// title: `获取信息失败,请稍后重试`,
// })
// .then(() => {
// // this.$router.go(-1);
// })
// .catch((err) => {
// // this.$router.go(-1);
// });
// });
},
reloadfn(msg) {
this.$nextTick(() => {
this.$refs.alert
.init({
confirmTxt: "我知道了",
title: `加入房间失败,重新加入`,
})
.then(() => {
// location.reload();
})
.catch((err) => {
// location.reload();
});
});
},
clearTime() {
clearInterval(this.askTimeFn);
clearInterval(this.answerTimeFn);
clearInterval(this.useTimeFn);
clearInterval(this.loseTimeFn);
},
// 清除直播时间相关的seession
clearSession() {
sessionStorage.removeItem("TIME_askTime");
sessionStorage.removeItem("TIME_answerTime");
sessionStorage.removeItem("TIME_useTime");
sessionStorage.removeItem("TIME_loseTime");
},
},
beforeDestroy() {
this.clearTime();
},
};
</script>
<style lang="less" scoped>
.livebox {
display: flex;
flex-direction: column;
//position: absolute;
//top: 0;
//left: 0;
//right: 0;
//bottom: 0;
background: #000000;
.top {
display: flex;
margin-top: 5%;
justify-content: space-between;
.top-left {
margin-left: 24px;
.title {
width: 72px;
height: 22px;
font-size: 18px;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 700;
color: #ffffff;
line-height: 22px;
}
.time-message {
margin-top: 20px;
display: flex;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #ffffff;
line-height: 20px;
p {
margin-right: 20px;
}
}
}
.top-right {
margin: 15px 25px 0 0;
display: flex;
align-items: center;
justify-content: center;
.icon {
width: 32px;
height: 32px;
border-radius: 50%;
cursor: pointer;
}
.close {
width: 102px;
height: 32px;
background: #ff4d4f;
border-radius: 2px;
font-size: 14px;
color: #ffffff;
line-height: 32px;
text-align: center;
margin-left: 20px;
cursor: pointer;
}
}
}
.main {
margin-top: 24px;
display: flex;
flex: 1;
.viedo-wrapper {
display: flex;
width: 49%;
height: 50vh;
background: #2d2d2d;
&:first-of-type {
margin-right: 2%;
}
.text {
display: flex;
flex-direction: column-reverse;
width: 112px;
margin-left: 24px;
color: #fff;
padding-bottom: 20px;
}
.time {
display: flex;
flex-direction: column-reverse;
padding-bottom: 20px;
width: 100px;
margin-right: 24px;
margin-left: 5px;
color: #fff;
.time-content {
.call-btn {
display: flex;
justify-content: center;
align-items: center;
min-width: 100px;
margin-bottom: 5px;
}
font-size: 12px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-end;
}
}
.viedo {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.user-icon {
position: absolute;
right: 0;
bottom: 20px;
display: block;
width: 32px;
height: 32px;
z-index: 100;
}
& > div {
height: 100%;
width: 100%;
video {
width: 100%;
height: 100%;
}
}
.video-icon {
width: 96px;
height: 96px;
}
.mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #2d2d2d;
display: flex;
justify-content: center;
align-items: center;
img {
display: block;
width: 93px;
height: 125px;
}
.vocie {
width: 93px;
height: 93px;
}
}
}
}
}
.out {
width: 102px;
height: 32px;
border-radius: 2px;
border: 1px solid #ffffff;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #ffffff;
text-align: center;
line-height: 32px;
cursor: pointer;
margin: 15px auto 6%;
}
}
</style>
class RtcClient {
constructor(options) {
this.sdkAppId_ = options.sdkAppId;
this.userId_ = options.userId;
this.userSig_ = options.userSig;
this.roomId_ = options.roomId;
this.role = options.role; // 用户的角色主播还是观众(这里的指的是trtc定义的anchor和audience)
this.nick = options.nick;
this.vueInstance = options.vueInstance;
this.isJoined_ = false;
this.isPublished_ = false;
this.isAudioMuted = false;
this.isVideoMuted = false;
this.localStream_ = null;
this.remoteStreams_ = [];
this.members_ = new Map();
this.inmembers_ = new Map();
this.viewslist = [];
this.isPushing = 0;
try {
this.client_ = TRTC.createClient({
mode: 'live',
sdkAppId: this.sdkAppId_,
userId: this.userId_,
userSig: this.userSig_
});
this.handleEvents();
} catch (error) {
console.log('创建client失败', error)
this.vueInstance.reloadfn()
}
}
// 加入房间
async join() {
if (this.isJoined_) {
// alert('抱歉,您已经在房间里面为了')
return;
}
try {
await this.client_.join({
roomId: this.roomId_,
role: this.role
});
console.log('加入房间trtc成功');
this.isJoined_ = true;
// 不要指定cameraId/microphoneId以避免过度约束
this.localStream_ = TRTC.createStream({
audio: true,
video: false,
userId: this.userId_,
mirror: true
});
this.startRTC()
} catch (e) {
console.error('加入房间失败 ' + e);
this.vueInstance.reloadfn()
}
}
// 打开摄像头
async startRTC() {
// 连麦观众角色切换为主播,开始推流
// 设置视频分辨率等参数
this.localStream_.setVideoProfile({
width: this.vueInstance.viedoParams.webVideoWidth,
height: this.vueInstance.viedoParams.webVideoHeight,
frameRate: this.vueInstance.viedoParams.webVideoFramerate,
bitrate: this.vueInstance.viedoParams.webVideoBitrate /* kpbs */
});
// 避免重复开摄像头
this.stopPush()
}
// 设置本地流
async setLocalVideo() {
if (!this.isJoined_) {
console.warn('请先加入房间');
return;
}
if (this.isPublished_) {
console.warn('您已经发布过了');
return;
}
try {
this.localStream_.initialize().catch(error => {
this.vueInstance.$message({
message: '打开设备失败,请检查您的设备!',
type: 'error'
});
console.error('failed initialize localStream ' + error);
}).then(() => {
// 本地流在主播放器上播放,并且插入到一个关联的box中
var localVideoWrapEl = document.getElementById('ask');
this.localStream_.play(localVideoWrapEl, {
muted: true
});
//主播直接推流
if (this.role == 'anchor') {
this.publish()
}
}).catch(error => {
this.vueInstance.$message({
message: '麦克风打开失败!',
type: 'error'
});
});
} catch (e) {
this.isPublished_ = false;
}
this.isPublished_ = true;
}
//发布本地流
async publish() {
this.client_ && this.client_.publish(this.localStream_).then(() => {
console.log('本地流发布成功')
this.isPublished_ = true;
// 手动将麦克风打开
this.unmuteLocalAudio()
this.vueInstance.isMicOn = true
}).catch(error => {
console.log('本地流发布失败')
this.isPublished_ = false;
this.vueInstance.reloadfn()
});
}
/**
* 为避免重复推流,先结束当前推流
*/
async stopPush() {
if (this.localStream_ && this.isPublished_ && this.isJoined_) {
this.client_.unpublish(this.localStream_).then(() => {
this.isPublished_ = false;
this.localStream_.stop();
this.setLocalVideo()
});
} else {
this.setLocalVideo()
}
}
// 退出
async leave() {
if (!this.isJoined_) {
console.warn('leave() - please join() firstly');
return;
}
// 如果是正在发布流,就先停止流
await this.unpublish();
// 离开房间
await this.client_.leave();
if (this.localStream_) {
this.muteLocalAudio()
this.localStream_.stop();
this.localStream_.close();
this.localStream_ = null;
}
this.isJoined_ = false;
}
// 退出本地流
async unpublish() {
if (!this.isJoined_) {
console.warn('unpublish() - please join() firstly');
return;
}
if (!this.isPublished_) {
console.warn('RtcClient.unpublish() called but not published yet');
return;
}
await this.client_.unpublish(this.localStream_);
this.isPublished_ = false;
}
//禁用音频轨道
//对于本地流,调用该方法会停止发送音频,远端会触发 Client.on('mute-audio') 事件。
//对于远端流,调用该方法会停止播放音频,但是仍然接收音频数据。
muteLocalAudio() {
this.localStream_.muteAudio();
}
//启用音频轨道
//对于本地流,调用该方法会触发远端 Client.on('unmute-audio') 事件。
//音频轨道默认是开启的,若你调用 muteAudio() 后可用该方法重新启用音频。
unmuteLocalAudio() {
this.localStream_.unmuteAudio();
}
// 禁用视频轨道
// 对于本地流,调用该方法会停止发送视频,远端会触发 Client.on('mute-video') 事件。
// 如果视频是从摄像头采集,此时摄像头灯仍然是亮着的。若想完全禁用视频轨道(即关闭摄像头) ,
// 可以使用 removeTrack() 删除视频轨道然后调用 MediaStreamTrack.stop() 关闭视频轨道(关闭摄像头)。
// 对于远端流,调用该方法会停止播放视频,但是仍然接收视频数据.
muteLocalVideo() {
this.localStream_.muteVideo();
}
//恢复播放音视频
//在某些版本浏览器上移动传入 play() 的 div 容器可能会导致音视频播放器进入 ‘PAUSED’ 状态,此时 需要调用该接口恢复播放。
//由于浏览器自动播放策略的限制,在 play() 返回 PLAY_NOT_ALLOWED 错误后需要引导用户通过手势 调用该接口恢复播放
resumeStreams() {
this.localStream_.resume();
for (let stream of this.remoteStreams_) {
stream.resume();
}
}
// client的一些事件监听
handleEvents() {
// 报错
this.client_.on('error', err => {
console.log('client 报错了--------------------------------------------------------------')
console.log(err)
// alert(err);
window.onbeforeunload = null
location.reload();
});
// 房间被解散了
this.client_.on('client-banned', err => {
console.log('房间被解散了');
});
// 当一个远程同伴(必须推流)进入房间时触发
this.client_.on('peer-join', evt => {
const userId = evt.userId;
console.log('有远程同伴进入房间:', userId)
});
// 当远处的同伴离开房间时触发(删减好友列表)
this.client_.on('peer-leave', evt => {
const userId = evt.userId;
console.log('有远程同伴离开房间:' + userId);
this.remove(userId)
});
// 在添加远程流时触发
this.client_.on('stream-added', evt => {
const remoteStream = evt.stream;
// 获取流的StreamId
const id = remoteStream.getId();
// 获取UserId
const userId = remoteStream.getUserId();
this.members_.set(userId, remoteStream);
this.inmembers_.set(userId, remoteStream);
console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
// 我们订阅远端的流
console.log('subscribe to this remote stream');
this.client_.subscribe(remoteStream);
});
// 在订阅远程流时触发
this.client_.on('stream-subscribed', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
const uid = remoteStream.userId_;
this.remoteStreams_.push(remoteStream);
remoteStream.on('player-state-changed', event => {
console.log(`${event.type} player is ${event.state}`);
// 远端流是播放还是暂停状态(显示按钮等画面不同)
if (event.type == 'video' && event.state == 'STOPPED') {
console.log(`${uid}----------远端流为停止`);
this.changeView(uid, 'mask', true)
}
if (event.type == 'video' && event.state == 'PAUSED') {
console.log(`${uid}----------远端流为暂停`);
this.changeView(uid, 'mask', true)
}
if (event.type == 'video' && event.state == 'PLAYING') {
console.log(`${uid}----------远端流为播放`);
this.changeView(uid, 'mask', false)
}
});
// 避免重复加载
for (let i = 0; i < this.viewslist.length; i++) {
if (this.viewslist[i] && uid == this.viewslist[i].userId) {
return
}
}
let isMask = false
// this.viewslist.push({ id: id, userId: uid, nick: uid, mask: isMask, vioce: true })
this.add(id, uid, isMask)
this.vueInstance.addNewMember(uid)
setTimeout(() => {
// 避免其他乱入视频
let index = this.viewslist.findIndex((item => {
if (item && item.userId) {
return item.userId == uid
} else {
return -1
}
}))
if (index < 0) {
return
}
// 播放视频
remoteStream.play(id);
if (!remoteStream.hasVideo()) {
this.changeView(id, 'mask', true)
}
if (!remoteStream.hasAudio()) {
this.changeView(id, 'vioce', false)
}
}, 1000)
});
// 当远程流被移除时触发
this.client_.on('stream-removed', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
const uid = remoteStream.userId_;
console.log(`${uid}------------远程流被移除时触发`);
// 停止播放并删除相应<video>标签
remoteStream.stop();
this.inmembers_.delete(uid)
this.remoteStreams_ = this.remoteStreams_.filter(stream => {
return stream.getId() !== id;
});
this.remove(uid)
});
// 流更新
this.client_.on('stream-updated', evt => {
console.log('=========流更新========stream-updated===================');
console.log(evt);
const remoteStream = evt.stream;
let uid = this.getUidByStreamId(remoteStream.getId());
// remoteStream.hasVideo() // 是否有视频轨道
// remoteStream.hasAudio() //是否有音轨道
// remoteStream.getType() // 主要用于判断一个远端流是主音视频流还是辅路视频流,辅路视频流通常是一个屏幕分享流。
console.log('remoteStream ID: ' + remoteStream.getId() + ' was updated hasAudio: ' +
remoteStream.hasAudio() + ' hasVideo: ' + remoteStream.hasVideo());
});
// 关闭音轨道
this.client_.on('mute-audio', evt => {
this.changeView(evt.userId, 'vioce', false)
console.log(evt.userId + '关闭麦克风============================================');
});
// 打开音轨道
this.client_.on('unmute-audio', evt => {
this.changeView(evt.userId, 'vioce', true)
if (evt.type == 'audio' && evt.state == 'PLAYING' && this.vueInstance.type == 1) {
this.changeView(evt.userId, 'mask', true)
}
});
//关闭视频轨道
this.client_.on('mute-video', evt => {
console.log(evt.userId + '关闭 video==============================================');
this.changeView(evt.userId, 'mask', true);
});
// 打开视频轨道
this.client_.on('unmute-video', evt => {
console.log(evt.userId + '打开 video=============================================');
if (this.members_.get(evt.userId)) {
this.changeView(evt.userId, 'mask', false);
}
});
}
getUidByStreamId(streamId) {
for (let [uid, stream] of this.members_) {
if (stream.getId() == streamId) {
return uid;
}
}
}
// 移除视频数组
remove(userId) {
for(let i = 0; i < this.viewslist.length; i++) {
if (this.viewslist[i] && this.viewslist[i].userId == userId) {
this.viewslist[i] = null
}
}
this.vueInstance.removeMember(userId)
};
// 改变视频数组属性
changeView(id,attr,val) {
for (let i = 0; i < this.viewslist.length; i++) {
if (this.viewslist[i] && (this.viewslist[i].userId == id || this.viewslist[i].id == id)) {
this.viewslist[i][attr] = val
}
}
}
add(id, uid, isMask) {
this.vueInstance.memberList.forEach((ele,index) => {
if (ele.liveUserId == uid) {
this.viewslist[index] = { id: id, userId: uid, nick: uid, mask: isMask, vioce: true }
}
});
}
}
export default RtcClient
...@@ -5,7 +5,7 @@ export const containObject = function(...obj1) { ...@@ -5,7 +5,7 @@ export const containObject = function(...obj1) {
return obj return obj
} }
// 获取页面自适应高度 // 获取页面自适应高度
export function resizeHeight(cMinusHeight = 152, iMinuxHeight = 210, refHeightId = 'slidebar-container', export function resizeHeight(cMinusHeight = 152, iMinuxHeight = 210, refHeightId = 'slidebar-container',
containerHeightId = 'screenSet'){ containerHeightId = 'screenSet'){
// let containerHeight = p_getElm(refHeightId).getBoundingClientRect().height - 15; // let containerHeight = p_getElm(refHeightId).getBoundingClientRect().height - 15;
let containerHeight = document.body.clientHeight - 80; let containerHeight = document.body.clientHeight - 80;
...@@ -800,7 +800,7 @@ export const betaHandle = (limit) => { ...@@ -800,7 +800,7 @@ export const betaHandle = (limit) => {
} }
//转换年月日方法 //转换年月日方法
export const timeHandle = (str) => { export const timeHandle = (str) => {
let date = new Date(str * 1); let date = new Date(str * 1);
let Y = date.getFullYear() + '-'; let Y = date.getFullYear() + '-';
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
...@@ -809,10 +809,48 @@ export const timeHandle = (str) => { ...@@ -809,10 +809,48 @@ export const timeHandle = (str) => {
let m = change(date.getMinutes()); let m = change(date.getMinutes());
return Y + M + D + h + m return Y + M + D + h + m
} }
//补0操作 //补0操作
const change = (num) => { const change = (num) => {
if(parseInt(num) < 10){ if(parseInt(num) < 10){
num = '0'+ num; num = '0'+ num;
} }
return num; return num;
} }
\ No newline at end of file
export const bindDragHeader = (classname,content) =>{
const dragDom = document.querySelector(classname);
const con = document.querySelector(content);
let translate,contranslate;
dragDom.onmousedown = (e) => {
const disX = e.clientX;
const disY = e.clientY;
translate = dragDom.style.transform.replace(/[^0-9\-,]/g,'').split(',');
contranslate = con.style.transform.replace(/[^0-9\-,]/g,'').split(',');
con.style.transition = "transform 100ms liner";
document.onmousemove = function (e) {
const l = e.clientX - disX;
const t = e.clientY - disY;
let x, y, tran;
if(contranslate.length> 1){
x= (l+ Number(contranslate[0])) + 'px';
y = (t + Number(contranslate[1])) + 'px';
}else{
x= (l) + 'px';
y = (t ) + 'px';
}
tran = `translate(${x},${y})`;
con.style.transform = tran;
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}
}
...@@ -99,7 +99,6 @@ ...@@ -99,7 +99,6 @@
</div> </div>
</template> </template>
<script> <script>
import RtcClient from "@/utils/live/rtc-client.js";
import { import {
getLiveTimeText, getLiveTimeText,
countDown, countDown,
...@@ -107,6 +106,7 @@ import { ...@@ -107,6 +106,7 @@ import {
laseTime, laseTime,
lastm, lastm,
} from "@/utils/live"; } from "@/utils/live";
import RtcClient from "../../../utils/live/RtcClient.js";
import { openLoading, closeLoading } from "@/utils/utils"; import { openLoading, closeLoading } from "@/utils/utils";
import alert from "@/components/common/alert.vue"; import alert from "@/components/common/alert.vue";
import timeLeft from "@/components/timeLeft"; import timeLeft from "@/components/timeLeft";
...@@ -390,7 +390,6 @@ export default { ...@@ -390,7 +390,6 @@ export default {
}, },
// 显示文案 // 显示文案
showText(status, role, item) { showText(status, role, item) {
console.log("status", status);
// 1 呼叫中 2 接入 3离线 // 1 呼叫中 2 接入 3离线
if (item.status == 2) { if (item.status == 2) {
item.timeleft = 0; item.timeleft = 0;
...@@ -555,19 +554,19 @@ export default { ...@@ -555,19 +554,19 @@ export default {
// 获取信息失败 // 获取信息失败
getErr() { getErr() {
closeLoading(this); closeLoading(this);
this.$nextTick(() => { // this.$nextTick(() => {
this.$refs.alert // this.$refs.alert
.init({ // .init({
confirmTxt: "我知道了", // confirmTxt: "我知道了",
title: `获取信息失败,请稍后重试`, // title: `获取信息失败,请稍后重试`,
}) // })
.then(() => { // .then(() => {
this.$router.go(-1); // // this.$router.go(-1);
}) // })
.catch((err) => { // .catch((err) => {
this.$router.go(-1); // // this.$router.go(-1);
}); // });
}); // });
}, },
reloadfn(msg) { reloadfn(msg) {
this.$nextTick(() => { this.$nextTick(() => {
......
<template> <template>
<div class="diagnosis-list-content"> <div class="diagnosis-list-content">
<div class="select-content screenSet"> <div class="select-content screenSet">
<div class="title">我的工作台</div> <div class="title">我的工作台</div>
</div> </div>
<div class="select-content screenSet"> <div class="select-content screenSet">
<!-- 时间 --> <!-- 时间 -->
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
<el-radio-button label="Cancelled">已取消(0)</el-radio-button> <el-radio-button label="Cancelled">已取消(0)</el-radio-button>
</el-radio-group> </el-radio-group>
</div> </div>
<div class="lfet" v-else > <div class="lfet" v-else >
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="6"><div class="grid-content bg-purple"> <el-col :span="6"><div class="grid-content bg-purple">
...@@ -70,19 +70,23 @@ ...@@ -70,19 +70,23 @@
</el-pagination> </el-pagination>
</el-row> </el-row>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
components: {
},
data(){ data(){
return { return {
tabPosition:"all", tabPosition:"all",
value1:Date.now(), value1:Date.now(),
currentPage4:0, currentPage4:0,
// 判断是否是管理员 // 判断是否是管理员
isdon:'1', isdon:'1',
name:'' showChat: true
} }
}, },
methods:{ methods:{
...@@ -134,7 +138,7 @@ export default { ...@@ -134,7 +138,7 @@ export default {
} }
} }
} }
.el-row { .el-row {
margin-bottom: 20px; margin-bottom: 20px;
&:last-child { &:last-child {
...@@ -165,6 +169,6 @@ export default { ...@@ -165,6 +169,6 @@ export default {
padding: 10px 0; padding: 10px 0;
background-color: #f9fafc; background-color: #f9fafc;
} }
</style>node
\ No newline at end of file </style>
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册