Commit a4867188 authored by 罗超's avatar 罗超

教室管理

parent 9de1fefc
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
"cos-js-sdk-v5": "^1.1.5", "cos-js-sdk-v5": "^1.1.5",
"echarts": "^5.1.2", "echarts": "^5.1.2",
"element-ui": "^2.14.1", "element-ui": "^2.14.1",
"flv.js": "^1.6.2",
"html2canvas": "^1.3.2", "html2canvas": "^1.3.2",
"js-md5": "^0.7.3", "js-md5": "^0.7.3",
"jsencrypt": "^3.2.1", "jsencrypt": "^3.2.1",
...@@ -36,6 +37,8 @@ ...@@ -36,6 +37,8 @@
"quasar": "^1.0.0", "quasar": "^1.0.0",
"relation-graph": "^1.0.8", "relation-graph": "^1.0.8",
"v-viewer": "^1.5.1", "v-viewer": "^1.5.1",
"videojs-flash": "^2.2.1",
"videojs-swf": "^5.4.2",
"vue-amap": "^0.5.10", "vue-amap": "^0.5.10",
"vue-apexcharts": "^1.6.2", "vue-apexcharts": "^1.6.2",
"vue-draggable-resizable": "^1.7.2", "vue-draggable-resizable": "^1.7.2",
......
...@@ -51,11 +51,14 @@ module.exports = function (ctx) { ...@@ -51,11 +51,14 @@ module.exports = function (ctx) {
// API: 'http://192.168.10.46:8300/api', // API: 'http://192.168.10.46:8300/api',
// API: 'https://eduapi.oytour.com/api', // API: 'https://eduapi.oytour.com/api',
API_ZC: 'http://192.168.10.17:8087/api', API_ZC: 'http://192.168.10.17:8087/api',
API_SK: 'ws:192.168.10.214:' API_SK: 'ws:192.168.10.214:',
API_SIG: 'http://localhost:5001/messagecenter'
} : { } : {
API: 'https://eduapi.oytour.com/api', API: 'https://eduapi.oytour.com/api',
API_ZC: 'http://propertyedu.oytour.com/api', API_ZC: 'http://propertyedu.oytour.com/api',
API_SK: 'ws://47.96.23.199:' API_SK: 'ws://47.96.23.199:',
API_SK_WSS: 'wss://sk.kookaku.com',
API_SIG: 'http://eduapi.oytour.com/messagecenter'
}, },
// transpile: false, // transpile: false,
...@@ -75,12 +78,11 @@ module.exports = function (ctx) { ...@@ -75,12 +78,11 @@ module.exports = function (ctx) {
// https://quasar.dev/quasar-cli/handling-webpack // https://quasar.dev/quasar-cli/handling-webpack
extendWebpack(cfg) { extendWebpack(cfg) {
// cfg.module.rules.push({ cfg.module.rules.push({
// enforce: 'pre', enforce: 'pre',
// test: /\.(js|vue)$/, test: /\.swf$/,
// loader: 'eslint-loader', loader: 'url-loader'
// exclude: /node_modules/ })
// })
} }
}, },
...@@ -188,30 +190,30 @@ module.exports = function (ctx) { ...@@ -188,30 +190,30 @@ module.exports = function (ctx) {
background_color: '#ffffff', background_color: '#ffffff',
theme_color: '#027be3', theme_color: '#027be3',
icons: [{ icons: [{
src: 'icons/icon-128x128.png', src: 'icons/icon-128x128.png',
sizes: '128x128', sizes: '128x128',
type: 'image/png' type: 'image/png'
}, },
{ {
src: 'icons/icon-192x192.png', src: 'icons/icon-192x192.png',
sizes: '192x192', sizes: '192x192',
type: 'image/png' type: 'image/png'
}, },
{ {
src: 'icons/icon-256x256.png', src: 'icons/icon-256x256.png',
sizes: '256x256', sizes: '256x256',
type: 'image/png' type: 'image/png'
}, },
{ {
src: 'icons/icon-384x384.png', src: 'icons/icon-384x384.png',
sizes: '384x384', sizes: '384x384',
type: 'image/png' type: 'image/png'
}, },
{ {
src: 'icons/icon-512x512.png', src: 'icons/icon-512x512.png',
sizes: '512x512', sizes: '512x512',
type: 'image/png' type: 'image/png'
} }
] ]
} }
}, },
...@@ -252,7 +254,7 @@ module.exports = function (ctx) { ...@@ -252,7 +254,7 @@ module.exports = function (ctx) {
// More info: https://quasar.dev/quasar-cli/developing-electron-apps/node-integration // More info: https://quasar.dev/quasar-cli/developing-electron-apps/node-integration
nodeIntegration: true, nodeIntegration: true,
extendWebpack( /* cfg */ ) { extendWebpack( /* cfg */) {
// do something with Electron main process Webpack cfg // do something with Electron main process Webpack cfg
// chainWebpack also available besides this extendWebpack // chainWebpack also available besides this extendWebpack
} }
......
...@@ -25,7 +25,7 @@ export default { ...@@ -25,7 +25,7 @@ export default {
</script> </script>
<style> <style>
@import url("//at.alicdn.com/t/font_2077629_0n6sqaodu4f.css"); @import url("//at.alicdn.com/t/font_2077629_s183916p0bb.css");
@font-face { @font-face {
font-family: "din"; font-family: "din";
src: url("./assets/font/DIN-Bold.otf") format("opentype"); src: url("./assets/font/DIN-Bold.otf") format("opentype");
......
@font-face { @font-face {
font-family: 'perfectFont'; font-family: 'perfectFont';
src: url('../font/MicrosoftYaHeiLight.woff') format('woff'); src: url('../font/MicrosoftYaHeiLight.woff') format('woff');
} }
@font-face { @font-face {
font-family: 'pingfang'; font-family: 'pingfang';
src: url('../font/PingFang2.ttf'); src: url('../font/PingFang2.ttf');
}
.perfectFont {
font-family: 'perfectFont' !important;
} }
...@@ -389,7 +389,7 @@ export default { ...@@ -389,7 +389,7 @@ export default {
that.injectTCCC( that.injectTCCC(
r.Data.Token, r.Data.Token,
1400617921, 1400617921,
this.userInfo.Email, that.userInfo.Email,
r.Data.SdkURL r.Data.SdkURL
); );
} }
......
<template> <template>
<div class="page-body room-list" v-loading="loading"> <div class="page-body room-list" v-loading="loading">
<div v-for="item in classdata" :key="item.Key" class="school-item"> <div v-for="(item, i) in classdata" :key="item.Key">
<div class="school-name">{{item.SchoolName}}</div> <template
<div class="classroom-box"> v-if="
<div v-for="(_item,_index) in item.RoomList" :key="item.RoomId" > item.SchoolName.indexOf('武侯') == -1 &&
<div class="classroom-item q-mr-lg q-mb-lg" :class="{'classroom-item-noUse':_item.UserRate==0}" > item.SchoolName.indexOf('眉山') == -1
"
<div @click="goUrl(item,_item)"> >
<div class="divCalssRoom"> <el-image class="classroom-img" :src="_item.RoomPicList[0]" fit="cover" > <div class="row items-center">
<div slot="error" class="image-slot"> <div class="text-h6 col">{{ item.SchoolName }}</div>
<img src="../../assets/images/classroom/bg-img.jpg" alt="" srcset="" style=" opacity: 0.8;"> <div v-if="i == 0" class="f12">只看在线:</div>
</div> <div v-if="i == 0">
</el-image></div> <el-switch
v-model="queryStatus"
</div> @change="changeViewStatusHandler"
></el-switch>
</div>
<div class="classroom-name" >{{_item.RoomName}} </div>
<!-- <img src="../../assets/images/classroom/warning.png" class="q-ml-sm" v-if="_item.warn"> <div class="q-mt-md q-mb-lg row">
<img src="../../assets/images/classroom/normal.png" class="q-ml-sm" v-if="!_item.warn"> --> <template v-for="(x, xi) in item.RoomList">
</div> <div
<q-knob class="class-room"
show-value style="width:calc(20% - 10px);margin-right:10px;margin-bottom:10px;"
font-size="0.1rem" v-if="queryStatus == 0 || (queryStatus == 1 && x.StatusInfo)"
v-model="_item.user" :key="xi"
size="50px" >
:thickness="0.1" <div class="img-box">
color="primary" <q-img
track-color="grey-3" :src="x.RoomPicList[0]"
class="q-ma-md classroom-user" height="100%"
readonly fit="fill"
style="text-align: center" spinner-color="primary"
spinner-size="30px"
> v-if="x.RoomPicList && x.RoomPicList.length > 0"
{{ _item.UserRate}}% />
<br/> <q-img
使用率 src="../../assets/images/classroom/bg-img.jpg"
</q-knob> fit="fill"
<div class="classroom-info"> height="100%"
<div v-if="_item.TimeList.length>0"> spinner-color="primary"
<q-carousel spinner-size="30px"
v-model="_item.slide" v-else
animated />
:navigation="false" </div>
height="75px" <div class="xuhua"></div>
class=" text-white shadow-1 rounded-borders" <div class="q-pa-md">
arrows <div class="row items-end">
control-color="grey-1" <div style="font-size:18px;" class="col">
control-text-color="primary" {{ x.RoomName }}
control-type="unelevated" </div>
> <div class="f12 q-ml-md text-grey-5">
<q-carousel-slide v-for="(slide,slideIndex) in _item.TimeList" :key="slideIndex" :name="slideIndex" > <div class="text-right">时段</div>
<div class="info-box"> <div class="text-dark">
<div class="time">{{ slide.StartTime }}</div> {{ x.StartTime }} - {{ x.EndTime }}
<div class="info">{{slide.TeacherName}}{{slide.ClassName}}</div>
</div>
</q-carousel-slide>
</q-carousel>
</div>
<div v-if="_item.TimeList.length===0" class="no-data">
暂无课程安排
</div>
</div> </div>
</div> </div>
</div>
<div class="q-mt-lg f12 row items-center">
<div class="text-grey-6 f12 col">
<span
class="q-px-xs text-white q-mr-md inline-block text-weight-bold"
style="border-radius:4px;font-size:15;"
:class="{
'bg-grey-3': !x.StatusInfo,
'bg-negative': x.StatusInfo
}"
>
LIVE
</span>
<span class="text-grey-6" v-if="!x.StatusInfo"
>设备离线</span
>
<span
class="text-grey-6"
v-else-if="x.StatusInfo.UnLockUserName == 'lock'"
>设备在线,未解锁</span
>
<span class="text-grey-6" v-else
>{{ x.StatusInfo.UnLockUserName }}正在使用</span
>
</div>
<div v-if="x.StatusInfo">
<q-btn
color="primary"
dense
class="f12"
flat
label="链接"
@click="openVideo(x)"
/>
</div>
</div>
</div>
</div> </div>
</div> </template>
</div>
</div> </template>
</div>
<q-dialog v-model="showVideo" @hide="clearVideo">
<q-card
class="my-card"
style="width:60vw !important;max-width:60vw !important;max-height:unset !important;"
flat
>
<!-- <q-card-section>
<div class="text-h6" v-if="currentRoom">
{{ currentRoom.RoomName }}
</div>
</q-card-section> -->
<q-card-section>
<video
id="my_video"
class="video-js vjs-default-skin"
controls
preload
v-if="showVideo"
></video>
</q-card-section>
</q-card>
</q-dialog>
</div> </div>
</template> </template>
<script> <script>
import { import { getUseClassRoomList } from "../../api/classroom/index";
getUseClassRoomList import * as signalR from "@aspnet/signalr";
} from '../../api/classroom/index'; import videojs from "video.js";
export default { import "videojs-contrib-hls";
meta: { export default {
title: "教室状态" meta: {
title: "教室状态"
},
data() {
return {
classdata: [],
loading: true,
showVideo: false,
videoObj: null,
currentRoom: null,
queryStatus: 0,
connection: "", //signalr连接
roomStatusList: [] //返回消息
};
},
created() {
let thisVue = this;
console.log(process.env.API_SIG);
this.connection = new signalR.HubConnectionBuilder()
.withUrl(process.env.API_SIG, {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets
})
.configureLogging(signalR.LogLevel.Information)
.build();
this.connection.on("RoomStatusChange", function(message) {
thisVue.roomStatusList = JSON.parse(message);
console.log(thisVue.roomStatusList);
thisVue.changeStatus();
});
this.connection.onclose(async () => {
setTimeout(() => {
thisVue.linkHub();
}, 3000);
});
this.linkHub();
},
mounted() {
this.getList();
//this.openVideo();
},
methods: {
changeViewStatusHandler() {},
clearVideo() {
this.videoObj.dispose();
this.showVideo = false;
}, },
openVideo(item) {
data() { this.showVideo = true;
return { this.currentRoom = item;
classdata:[ this.$nextTick(() => {
this.videoObj = videojs("my_video", {
controls: true, //是否显示播放器控件
autoplay: true, //是否自动播放
sources: [
{ {
SchoolName:"锦江校区一", type: "application/x-mpegURL",
list:[ src: `http://47.96.12.235:5000/live/${item.RoomId}.m3u8`
{
slide:0,
img:"",
classroom:'樱花教室',
user:80,
warn:0,
course:[]
},
]
} }
]
});
], this.videoObj.play();
loading: true, setTimeout(() => {
document
}; .getElementById("my_video_html5_api")
.setAttribute("controls", "controls");
}, 2000);
});
}, },
mounted(){ changeStatus() {
this.getList(); this.initStatus();
if (this.roomStatusList && this.roomStatusList.length > 0) {
this.roomStatusList.forEach(x => {
this.classdata.forEach(y => {
y.RoomList.forEach(z => {
if (z.RoomId == x.RoomId) {
z.StatusInfo = x;
}
});
});
});
}
this.$forceUpdate();
}, },
methods:{ initStatus() {
goUrl(x,y){ this.classdata.forEach(y => {
this.$router.push({ y.RoomList.forEach(z => {
path:"/classroom/useRecord", this.classdata.forEach(y => {
query:{ y.RoomList.forEach(z => {
key:x.Key, z.StatusInfo = null;
roomId:y.RoomId });
} });
}) });
}, });
getList() { this.$forceUpdate();
getUseClassRoomList({}).then(res => { },
this.loading = false linkHub() {
if(res.Code===1){ let thatVue = this;
res.Data.forEach(e => { this.connection
e.RoomList.forEach(_e=>{ .start()
_e.slide=0 .then(() => {
}) console.log("linke success");
}); thatVue.connection.invoke("RegistErp", "IMERP");
this.classdata=res.Data })
} .catch(e => {
}).catch(() => { console.log(e);
this.loading = false; setTimeout(() => {
}) thatVue.linkHub();
}, }, 3000);
} });
}; },
goUrl(x, y) {
</script> this.$router.push({
<style lang="scss" scoped> path: "/classroom/useRecord",
.school-item{ query: {
margin-bottom: 10px; key: x.Key,
roomId: y.RoomId
.school-name{
font-size: 16px;
font-family: Microsoft YaHei;
font-weight: bold;
color: #000000;
margin-bottom: 30px;
}
.classroom-box{
width: 1680px;
display: flex;
// justify-content: space-between;
flex-wrap: wrap;
.classroom-item-noUse{
opacity:0.8;
}
.classroom-item{
width: 390px;
height: 235px;
background: #FFFFFF;
border: 1px solid #EBEBEB;
box-shadow: 0px 1px 1px 0px #E4E7FB;
border-radius: 16px;
position: relative;
overflow: hidden;
.classroom-img{
display: block;
width: 390px;
// height: 160px;
// border-radius: 16px;
background-color: #333;
overflow: hidden;
}
.divCalssRoom{
height: 160px;
border-radius: 16px;
overflow: hidden;
}
.classroom-name{
position: absolute;
top: 20px;
left: 20px;
z-index: 5999;
font-size: 20px;
font-family: PingFang SC;
font-weight: 800;
color: #FFFFFF;
display: flex;
align-items: center;
}
.classroom-user{
position: absolute;
top: 10px;
right: 10px;
z-index: 5999;
color: #FFFFFF;
}
.classroom-info{
width: 390px;
height: 75px;
// background-color: tomato;
.info-box{
height: 43px;
display: flex;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
padding: 0 40px;
.time{
font-size: 14px;
font-family: PingFang SC;
font-weight: bold;
color: #6D97FF;
margin-right: 10px;
}
.info{
// width: 225px;
height: 100%;
font-size: 14px;
font-family: PingFang SC;
display: flex;
align-items: center;
color: #000000;
}
}
}
} }
});
},
getList() {
getUseClassRoomList({})
.then(res => {
this.loading = false;
if (res.Code === 1) {
res.Data.forEach(e => {
e.RoomList.forEach(_e => {
_e.slide = 0;
});
});
this.classdata = res.Data;
this.changeStatus();
}
})
.catch(() => {
this.loading = false;
});
} }
}
};
</script>
<style>
.class-room {
border: 1px solid #f5f5f5;
border-radius: 10px;
position: relative;
overflow: hidden;
}
.class-room .img-box {
height: 80px;
}
.class-room .xuhua {
position: absolute;
top: 0;
height: 80px;
left: 0;
right: 0;
backdrop-filter: blur(4px);
}
.class-room:hover .xuhua {
backdrop-filter: none;
}
.no-data {
height: 75px;
font-size: 14px;
color: #000000;
display: flex;
justify-content: center;
align-items: center;
}
.video-js div,
.video-js button {
display: none !important;
}
.video-js {
width: 100%;
} }
.no-data{ .video-js video {
height: 75px; width: 100%;
font-size: 14px;
color: #000000;
display: flex;
justify-content: center;
align-items: center;
} }
.room-list{ .video-js .no-video {
overflow: scroll; display: flex;
height: 100%;
font-size: 14px;
text-align: center;
justify-content: center;
align-items: center;
background: #000;
} }
</style> </style>
...@@ -191,6 +191,11 @@ export default { ...@@ -191,6 +191,11 @@ export default {
immediate: true immediate: true
} }
}, },
mounted() {
if (tccc && tccc.Agent) {
tccc.Agent.offline();
}
},
methods: { methods: {
callBackUserInfo(code) { callBackUserInfo(code) {
setUserCodeCallBack({ setUserCodeCallBack({
...@@ -228,7 +233,7 @@ export default { ...@@ -228,7 +233,7 @@ export default {
const s = document.createElement("script"); const s = document.createElement("script");
s.type = "text/javascript"; s.type = "text/javascript";
s.src = s.src =
"http://wwcdn.weixin.qq.com/node/wework/wwopen/js/wwLogin-1.2.5.js"; "https://wwcdn.weixin.qq.com/node/wework/wwopen/js/wwLogin-1.2.5.js";
const wxElement = document.body.appendChild(s); const wxElement = document.body.appendChild(s);
// 调用企业微信二维码方法 // 调用企业微信二维码方法
wxElement.onload = function() { wxElement.onload = function() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment