dbsd_kczx/src/views/kc/jiaoshi/index.vue

762 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div style="width:100%;height: 100%;">
<div class="" style="padding: 1rem 1rem 0 1rem;">
<a-row :gutter="[16,16]">
<a-col :span="10">
<a-card class="cardDiv" title="教室情况">
<!-- -<i class="fa-sharp fa-solid fa-user"></i>- -->
<a-row class="" :gutter="[16,16]">
<a-col :xs="{ span: 8 }" :sm="{ span: 8 }" :lg="{ span: 8 }">
<div class="numberBlob handleCss" @click="() => queryParam = {}">
<div class="numSpan">{{ allClassNum }}</div>
<div class="numberName">
<!-- <StockOutlined class="blue"/> -->
总数
</div>
</div>
</a-col>
<a-col :xs="{ span: 8 }" :sm="{ span: 8 }" :lg="{ span: 8 }">
<div class="numberBlob handleCss" @click="() => queryParam = {}">
<div class="numSpan blue">{{ leftList.length }}</div>
<div class="numberName">
<!-- <RiseOutlined class="orange"/> -->
可直播
</div>
</div>
</a-col>
<a-col :xs="{ span: 8 }" :sm="{ span: 8 }" :lg="{ span: 8 }">
<div class="numberBlob handleCss">
<div class="numSpan orange" style="font-size: 24px;height: 50px;line-height: 50px;">未检测</div>
<div class="numberName">
<!-- <RiseOutlined class="orange"/> -->
直播异常
</div>
</div>
</a-col>
</a-row>
<!-- <a-divider /> -->
<!-- <div class="buttomDiv"></div> -->
</a-card>
</a-col>
<a-col :span="14">
<a-card class="cardDiv" title="上课情况">
<!-- -<i class="fa-sharp fa-solid fa-user"></i>- -->
<a-row class="" :gutter="[16,16]">
<a-col :xs="{ span: 6 }" :sm="{ span: 6 }" :lg="{ span: 6 }">
<div class="numberBlob handleCss" @click="() => queryParam = {}">
<div class="numSpan">{{ (leftList.filter(x => x?.nowIsClass).length || 0) }}</div>
<div class="numberName">
<!-- <StockOutlined class="blue"/> -->
正在上课
</div>
</div>
</a-col>
<a-col :xs="{ span: 6 }" :sm="{ span: 6 }" :lg="{ span: 6 }">
<div class="numberBlob handleCss">
<div class="numSpan orange" style="font-size: 24px;height: 50px;line-height: 50px;">{{ getSysConfig().flag5 == 0?'否':'是' }}</div>
<div class="numberName">
<!-- <RiseOutlined class="orange"/> -->
开放听课
</div>
</div>
</a-col>
<a-col :xs="{ span: 6 }" :sm="{ span: 6 }" :lg="{ span: 6 }">
<div class="numberBlob handleCss" @click="() => queryParam = {}">
<div class="numSpan blue">{{ (leftList.filter(x => x?.child['教师近景']?.isOnLine).length || 0) }}</div>
<div class="numberName">
<!-- <RiseOutlined class="orange"/> -->
正在直播
</div>
</div>
</a-col>
<a-col :xs="{ span: 6 }" :sm="{ span: 6 }" :lg="{ span: 6 }">
<div class="numberBlob handleCss">
<div class="numSpan orange">{{ (leftList.filter(x => !x?.child['教师近景']?.isOnLine).length || 0) }}</div>
<div class="numberName">
<!-- <RiseOutlined class="orange"/> -->
直播异常
</div>
</div>
</a-col>
</a-row>
<!-- <a-divider /> -->
<!-- <div class="buttomDiv"></div> -->
</a-card>
</a-col>
</a-row>
</div>
<div class="" style="padding: 1rem;">
<a-row :gutter="[16,16]">
<a-col :span="6" v-for="(item,index) of cardList" :key="index">
<a-card bordered hoverable @click="() => currentCardIndex = index" :class="currentCardIndex == index?'active':''">
<div style="font-size: 15px;font-weight: 600;margin-bottom: 10px;">{{ item.jxlName }}</div>
<a-row style="margin-bottom: 10px;text-align: center;border-bottom: 1px #f0f0f0 solid ;padding-bottom: 10px;">
<a-col :span="8" style="font-weight: 600;">{{ item.jsNum }}</a-col>
<a-col :span="8" style="font-weight: 600;">{{ cardList.length }}</a-col>
<a-col :span="8" style="font-weight: 600;">{{ getSysConfig().flag5 == 0?'否':'是' }}</a-col>
<a-col :span="8">总数</a-col>
<a-col :span="8">可直播</a-col>
<a-col :span="8">开放听课</a-col>
</a-row>
<a-row style="text-align: center;">
<a-col :span="8" style="font-weight: 600;">{{ item.child.filter(x => x?.nowIsClass).length || 0}}间</a-col>
<a-col :span="8" style="font-weight: 600;">{{ item.child.filter(x => x?.child['教师近景']?.isOnLine).length || 0}}间</a-col>
<a-col :span="8" style="font-weight: 600;">{{ item.child.filter(x => !x?.child['教师近景']?.isOnLine).length || 0 }}间</a-col>
<a-col :span="8">正在上课</a-col>
<a-col :span="8">正在直播</a-col>
<a-col :span="8">直播异常</a-col>
</a-row>
</a-card>
</a-col>
</a-row>
</div>
<div class="jeecg-basic-table-form-container">
<a-form @keyup.enter.native="searchQuery" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-row :gutter="24">
<a-col :lg="8">
<a-form-item label="教学楼">
<!-- <j-dict-select-tag placeholder="请选择教室" v-model:value="queryParam.xqxn" dictCode="kc_xqxn_history,title,title"/> -->
<a-input placeholder="请输入教学楼" v-model:value="queryParam.jxlName"/>
</a-form-item>
</a-col>
<a-col :lg="8">
<a-form-item label="教室">
<a-input placeholder="请输入教室" v-model:value="queryParam.jsmc"/>
</a-form-item>
</a-col>
<a-col :lg="8">
<a-form-item label="直播功能">
<a-select placeholder="请选择直播功能" v-model:value="queryParam.sfyx">
<a-select-option :value="undefined">请选择</a-select-option>
<a-select-option value="1">是</a-select-option>
<a-select-option value="0">否</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="8">
<a-form-item label="开放听课">
<a-select placeholder="请选择开放听课" v-model:value="queryParam.sfyx2">
<a-select-option :value="undefined">请选择</a-select-option>
<a-select-option value="1">是</a-select-option>
<a-select-option value="0">否</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="8">
<a-form-item label="直播状态">
<a-select placeholder="请选择直播状态" v-model:value="queryParam.onLineClassType">
<a-select-option :value="undefined">请选择</a-select-option>
<a-select-option value="green">正常</a-select-option>
<a-select-option value="yellow">部分正常</a-select-option>
<a-select-option value="red">异常</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="8">
<a-form-item label="当前有课">
<a-select placeholder="请选择当前有课" v-model:value="queryParam.nowIsClass">
<a-select-option :value="undefined">请选择</a-select-option>
<a-select-option value="1">是</a-select-option>
<a-select-option value="0">否</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="8">
<a-form-item label="下节有课">
<a-select placeholder="请选择下节有课" v-model:value="queryParam.nextIsClass">
<a-select-option :value="undefined">请选择</a-select-option>
<a-select-option value="1">是</a-select-option>
<a-select-option value="0">否</a-select-option>
</a-select>
</a-form-item>
</a-col>
<!-- <a-col :xl="6" :lg="7" :md="8" :sm="24">
<span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
<a-col :lg="6"> -->
<!-- <a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button>
<a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset" style="margin-left: 8px">重置</a-button> -->
<!-- <a-button type="primary" preIcon="ant-design:check-square-outlined" @click="closeQuartz" v-if="shangXianQuartz.status == 0" style="margin-left: 8px">停止自动调整教室</a-button> -->
<!-- 永远开启 <a-button type="primary" preIcon="ant-design:border-outlined" @click="openQuartz" v-if="shangXianQuartz.status == -1" style="margin-left: 8px">开启自动调整教室</a-button> -->
<!--<a @click="toggleSearchStatus = !toggleSearchStatus" style="margin-left: 8px">
{{ toggleSearchStatus ? '收起' : '展开' }}
<Icon :icon="toggleSearchStatus ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
</a>-->
<!-- </a-col>
</span>
</a-col> -->
</a-row>
</a-form>
</div>
<a-table v-show="true" :loading="loading" :data-source="filterDataSource()" :pagination="false" bordered size="middle" class="ant-table-striped" :scroll="{ y: 650 }">
<a-table-column title="序号" align="center" data-index="index">
<template #default="{ index }">
{{ index+1 }}
</template>
</a-table-column>
<a-table-column title="教学楼" data-index="jxlName"/>
<a-table-column title="教室" data-index="jsmc"/>
<a-table-column title="直播功能" align="center" data-index="sfyx">
<template #default="{ text }">
{{ text == 0?'是':'否' }}
</template>
</a-table-column>
<a-table-column title="开放听课" align="center" data-index="sfyx">
<template #default="{ text }">
{{ text == 0?'是':'否' }}
</template>
</a-table-column>
<!-- 5.开放听课(是、否)?? -->
<a-table-column title="直播状态" align="center" data-index="child_教师近景">
<template #default="{ record }">
<span :class="getIsOnLineClass(record)">
<i class="fas fa-circle" />
</span>
</template>
</a-table-column>
<a-table-column title="当前有课" align="center" data-index="nowIsClass">
<template #default="{ record }">
{{ record?.nowIsClass == 0?'是':'否' }}
</template>
</a-table-column>
<a-table-column title="下节有课" align="center" data-index="nextIsClass">
<template #default="{ record }">
{{ record?.nextIsClass == 0?'是':'否' }}
</template>
</a-table-column>
<a-table-column title="备注" data-index="bfStatusInfo"/>
<!-- 9.相关操作
A预览绿灯、黄灯可预览红灯按钮变灰不可预览
b)开启直播/关闭直播
c)开放听课/关闭听课
10.备注显示黄灯原因ppt没有信号 -->
<!-- <a-table-column title="教师全景" align="center" data-index="child_教师全景">
<template #default="{ record }">
<span :class="record?.child['教师全景']?.isOnLine?'green':'red'">
<i class="fas fa-circle"/>
</span>
</template>
</a-table-column>
<a-table-column title="学生全景" align="center" data-index="child_学生全景">
<template #default="{ record }">
<span :class="record?.child['学生全景']?.isOnLine?'green':'red'">
<i class="fas fa-circle"/>
</span>
</template>
</a-table-column>
<a-table-column title="PPT" align="center" data-index="child_PPT">
<template #default="{ record }">
<span :class="record?.child['PPT']?.isOnLine?'green':'red'">
<i class="fas fa-circle"/>
</span>
</template>
</a-table-column>
<a-table-column title="直播推流" align="center" data-index="child_直播推流">
<template #default="{ record }">
<span :class="record?.child['教师全景']?.isOnLine?'green':'red'">
<i class="fas fa-circle"/>
</span>
</template>
</a-table-column> -->
<a-table-column width="200px" title="操作" data-index="action">
<template #default="{ record }">
<a :disabled="record.allIsOnLine != 0 " @click="ylLiveNew(record)" >预览 |</a>
<a @click="ylLive(record)" hidden>预览 |</a>
<a v-if="!record?.child['教师全景']?.isOnLine" @click="changeLive(record,true)">开启直播 |</a>
<a v-else @click="changeLive(record,false)">关闭直播 |</a>
<a v-if="record.sfyx == 1" @click="changeKt(record,true)">开放听课</a>
<a v-else-if="record.sfyx == 0" @click="changeKt(record,false)">关闭听课</a>
<!-- {{ record.id }} -->
</template>
</a-table-column>
</a-table>
</div>
<a-modal :visible="isShowAllLive" width="80%" style="top: 20px" title="直播" :ok-button-props="{ style: { display: 'none' } }" cancelText="关闭" @cancel="() => (isShowAllLive = false,showAllLiveRef.close())">
<showAllLive ref="showAllLiveRef" :currentItem="currentItem" :isShowAllLive="isShowAllLive"/>
</a-modal>
</template>
<script lang="ts" setup name="zhihuijiaoshiIndexPage">
import { defHttp } from '/@/utils/http/axios';
import { ref, onMounted, Ref, watch, reactive } from 'vue';
import { nextTick } from 'vue';
import videojs from "video.js";
import { useMessage } from '/@/hooks/web/useMessage';
import showAllLive from './showAllLive.vue';
import { execAvyApi, getAvyCtrlLiveOpenOrCloseUrl } from "/@/views/site/utils/index";
import { JInput } from '/@/components/Form';
import { resumeJob, pauseJob } from '/@/views/monitor/quartz/quartz.api';
import { useRouter } from 'vue-router';
import { getSysConfig } from '/@/views/site/utils/index';
// const _document:any = window.document;
const showAllLiveRef = ref();
const leftList:Ref<any> = ref([]);
const cardList:Ref<any> = ref([]);
const currentItem:Ref<any> = ref({});
const currentCardIndex:Ref<any> = ref(0);
const allClassNum:Ref<any> = ref(0);
// const topWidth:any = ref('0');
// const isfirst:any = ref(false);
const showAllLiveKey:Ref<string> = ref('showAllLiveKey');
const isShowAllLive:Ref<boolean> = ref(false);
const loading:Ref<boolean> = ref(false);
const { createMessage, createInfoModal } = useMessage();
const route = useRouter();
const queryParam:Ref<any> = ref({});
onMounted(() => {
loadData();
});
enum Api {
list = '/jiaoshi/kcZhihuijiaoshi/list',
updateAllLive = '/jiaoshi/kcZhihuijiaoshi/updateAllLive',
changeAvyLiveApi = '/httpinterface/runAvyApiByIds',
xxhbjsjbxxList = '/xxhbjsjbxx/xxhbjsjbxx/list',
}
/**
* 列表接口
* @param params
*/
const list = (params) => defHttp.get({ url: Api.list, params });
const updateAllLive = (params) => defHttp.get({ url: Api.updateAllLive, params });
const changeAvyLiveApi = (params) => defHttp.get({ url: Api.changeAvyLiveApi, params,timeout: 90000 });
const jsjbxxList = (params) => defHttp.get({ url: Api.xxhbjsjbxxList, params,timeout: 90000 });
const shangXianQuartz = ref<any>({});
const labelCol = reactive({
xs: { span: 24 },
sm: { span: 7 },
});
const wrapperCol = reactive({
xs: { span: 24 },
sm: { span: 16 },
});
const ipagination = ref(
{
current: 1,
pageSize: 10,
pageSizeOptions: ['10', '20', '30'],
showTotal: (total, range) => {
return range[0] + '-' + range[1] + ' 共' + total + '条';
},
showQuickJumper: true,
showSizeChanger: true,
total: 0,
}
);
function loadData(){
loading.value = true;
let getListAction:any = [];
let liveIsExist = (x) => {
return new Promise((resolve,reject) => {
videojs.xhr.get(x.pullUrl,(err, resp, body) => {
if(err){
reject(false);
x.isOnLine = false
}else{
resolve(true);
x.isOnLine = true
}
})
//此法不行,会跨域
// defHttp.get({ url: x.pullUrl }, { isExternal: true, withToken: true }).then(res => {
// resolve(true);
// x.isOnLine = true;
// }).catch(eres => {
// reject(false);
// x.isOnLine = false;
// })
})
}
list({ pageSize: -1, changshang: '奥威亚',...queryParam.value }).then(res => {
let list = (res?.records) ?? [];
//聚合
let map = {};
let jxlMap = {};
list.forEach(x => {
let item = map[x.jsmc];
x.isOnLine = false;
if(item){
item.child[x.xm] = x;
}else{
let child = {};
child[x.xm] = x;
map[x.jsmc] = {
...x,
child
};
item = map[x.jsmc];
}
});
leftList.value = Object.values(map);
leftList.value.forEach(x => {
let item = jxlMap[x.jxlId];
if(item){
item.child.push(x);
}else{
let child = [x];
jxlMap[x.jxlId] = {
...x,
child
};
item = jxlMap[x.jxlId];
}
});
cardList.value = Object.values(jxlMap);
loading.value = false;
nextTick(() => {
leftList.value.forEach(item => {
let child = item.child;
Object.values(child).forEach(item => {
let x:any = item;
// if(x.pullUrl == 'https://kczx.nenu.edu.cn:9553/live_hls/yfjxl101s_lbzj.m3u8')
getListAction.push(liveIsExist(x));
});
});
Promise.all(getListAction).then(resList => {
console.log(`🚀 ~ file: index.vue:104 ~ Promise.all ~ ress:`, resList);
// loading.value = false;
}).catch((e) => {
console.error(e);
// loading.value = false;
});
});
//计算左侧菜单高度
// let mainDiv:any = _document?.querySelector('.ant-layout .jeecg-default-layout-main > div');
// topWidth.value =mainDiv?.style?.height?? '0';
});
getAutoShangXianQuartz();
getJsjbxxList();
}
function getJsjbxxList(){
jsjbxxList({ pageSize: -1, changshang: '奥威亚',...queryParam.value }).then(res => {
let list = (res?.records) ?? [];
allClassNum.value = list.length;
});
}
//获取class样式
function getIsOnLineClass(record){
let resClas = 'red';
let allIsOnLine = record?.child['教师近景']?.isOnLine?1:0 + record?.child['教师全景']?.isOnLine?1:0 + record?.child['学生全景']?.isOnLine?1:0 + record?.child['PPT']?.isOnLine?1:0;
record.allIsOnLine = allIsOnLine;
let bfStatusInfo = '';
if(!record?.child['教师近景']?.isOnLine){
bfStatusInfo = '教师近景没有信号';
}else if(!record?.child['教师全景']?.isOnLine){
bfStatusInfo = '教师全景没有信号';
}else if(!record?.child['学生全景']?.isOnLine){
bfStatusInfo = '学生全景没有信号';
}else if(!record?.child['PPT']?.isOnLine){
bfStatusInfo = 'PPT没有信号';
}
record.bfStatusInfo = bfStatusInfo;
if(allIsOnLine == 4){
resClas = 'green';
}else if(allIsOnLine == 0){
resClas = 'red';
}else{
resClas = 'yellow';
}
return resClas;
}
function ylLive(record){
isShowAllLive.value = true
nextTick(() => {
currentItem.value = record
})
}
function ylLiveNew(record) {
let routeData = route.resolve({ path:'/site/liveRoom2',query:{ id: record.jsbh } });
window.open(routeData.href, '_blank');
}
function changeLive(record, isEnable){
console.log('createInfoModal ->',createInfoModal);
loading.value = true;
let ids:any = [];
let changeLiveEnd:any = [];
Object.values(record.child).forEach(x => {
let item:any = x;
//收集ID
if(item.xm == '教师近景'){
ids.push(item.id);
}
});
changeAvyLiveApi({ ids:ids.join(','), type: isEnable?1:0 }).then(res => {
console.log(`🚀 -------------------------------------------------------🚀`);
console.log(`🚀 ~ file: index.vue:295 ~ changeAvyLiveApi ~ res:`, res);
console.log(`🚀 -------------------------------------------------------🚀`);
let content = '';
res.forEach(x => {
// content += x.jsmc + "-" + x.xm
content += x.jsmc
let text = '';
if(x.resText){
if(x.resText.includes('ok')){
text = (isEnable?'开启':'关闭')+'直播-操作成功!'
}else{
text = (isEnable?'开启':'关闭')+'直播-操作失败:' + x.resText
}
}
content += " " + text + "<br/>"
});
if(content.includes('直播-操作成功!')){
//延迟12秒
setTimeout(() => {
loadData();
createInfoModal({ width:'50%', title: '结果',content })
}, 12*1000);
}else{
loadData();
createInfoModal({ width:'50%', title: '结果',content })
}
}).catch(e => {
console.error(e);
loading.value = false;
createInfoModal({title: '错误结果',content:e})
})
}
function changeKt(record, isEnable){
loading.value = true;
let ids:any = [];
Object.values(record.child).forEach(x => {
let item:any = x;
ids.push(item.id);
});
if(!ids) return;
updateAllLive({ ids: ids.join(','), sfyx: isEnable?0:1}).then(res => {
loadData();
}).catch(e => {
console.error(e);
loading.value = false;
})
}
function tableChange(pagination) {
ipagination.value.current = pagination.current;
ipagination.value.pageSize = pagination.pageSize;
loadData();
}
function filterDataSource() {
let dataSource:any = [];
let list = cardList.value[currentCardIndex.value]?.child??[];
let qw = queryParam.value;
list.forEach(x => {
let isReturn = true;
if(qw.jxlName){
isReturn = x.jxlName?.includes(qw.jxlName);
}
if(qw.jsmc){
isReturn = x.jsmc?.includes(qw.jsmc);
}
if(qw.sfyx2){
isReturn = x.jsmc?.includes(qw.sfyx2);
}
if(qw.sfyx){
isReturn = x.sfyx == qw.sfyx;
}
if(qw.onLineClassType){
isReturn = x.onLineClassType == qw.onLineClassType;
}
if(qw.nowIsClass){
isReturn = x.nowIsClass == qw.nowIsClass;
}
if(qw.nextIsClass){
isReturn = x.nextIsClass == qw.nextIsClass;
}
if(isReturn){
dataSource.push(x);
}
})
return dataSource;
}
/**
* 查询
*/
function searchQuery() {
loadData();
}
/**
* 重置
*/
function searchReset() {
queryParam.value = {};
//刷新数据
loadData();
}
function getAutoShangXianQuartz(){
defHttp.get({ url: '/jiaoshi/kcZhihuijiaoshi/getAutoShangXianQuartz', params: {} }).then(res => {
shangXianQuartz.value = res;
})
}
/**
* 启动
*/
async function openQuartz(){
await resumeJob({ id: shangXianQuartz.value.id }, loadData);
}
/**
* 暂停
*/
async function closeQuartz(){
await pauseJob({ id: shangXianQuartz.value.id }, loadData);
}
</script>
<style lang="less" scoped>
.videoMax{
width: 25%;
}
.videoCardMain {
:deep(.ant-card-body) {
padding: 0;
}
}
/* 隐藏video 进度条 */
video::-webkit-media-controls-timeline {
display: none;
}
.green {
color: green;
}
.red {
color: red;
}
.yellow {
color: yellow;
}
// .green {
// color: #1ab394;
// }
.orange {
color: #ed9535;
}
.blue {
color: #1c84c6;
}
.jeecg-basic-table-form-container {
.ant-form {
padding: 12px 10px 6px 10px;
margin-bottom: 8px;
background-color: #fff;
border-radius: 2px;
}
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
.query-group-cust{
width: calc(50% - 15px);
min-width: 100px !important;
}
.query-group-split-cust{
width: 30px;
display: inline-block;
text-align: center
}
}
.jeecg-basic-table-form-containera{
line-height: 24px;
background: #fff;
padding: 20px 0 0 10px;
margin-bottom: -20px;
}
.jeecg-basic-table .ant-table-wrapper .ant-table-title {
min-height: 0px !important;
padding: 0 0 8px 0 !important;
}
.active{
background-color: #36b395;
color: #ffffff;
}
.cardDiv {
:deep(.ant-divider-horizontal) {
margin: 12px 0;
}
:deep(.ant-card-head-title) {
font-weight: 700;
}
}
.numberBlob {
text-align: center;
// height: 100px;
height: 4rem;
.numSpan {
font-size: 2rem;
}
.numberName {
font-weight: 700;
}
.fs3r {
font-size: 2rem;
}
}
.handleCss{
cursor:pointer;
}
.iconel{
float: left;
width: 50px;
margin-right: 8px;
}
.iconer{
float: left;
}
.ictitle{
font-size: 15px;
color: #000000;
font-weight: 600;
text-align: left;
}
.ant-form-item{
margin-bottom: 10px;
}
</style>