You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
607 lines
18 KiB
607 lines
18 KiB
<template>
|
|
<el-row>
|
|
<el-col :span="14">
|
|
<div style="margin-bottom: 4px;">
|
|
<el-button icon="Plus" type="primary" @click="addUserData">
|
|
add User
|
|
</el-button>
|
|
<el-button icon="Delete" type="danger" @click="delUserData">
|
|
delete User
|
|
</el-button>
|
|
<el-button icon="Refresh" type="info" @click="init({})" text>
|
|
refresh
|
|
</el-button>
|
|
</div>
|
|
<div>
|
|
<vueTable ref="userTableParent" :refName="refName" :tableHeader="tableHeader" :tableData="tableData"
|
|
@detailInfo="detailInfo" @editInfo="editInfo" @updateMima="updateMima">
|
|
</vueTable>
|
|
<el-card>
|
|
<template #header>
|
|
<div>
|
|
<span>Cheat Pie Chart</span>
|
|
<!-- <el-button type="primary" @click="getzuobiList">获取作弊信息</el-button> -->
|
|
</div>
|
|
</template>
|
|
<div id="teacherZuobiEcharts"></div>
|
|
</el-card>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="10">
|
|
<el-row>
|
|
<el-col :span="12" v-for="studentItem in tableData" :key="studentItem._id">
|
|
<!-- <img v-if="imageSrc" :src="imageSrc" class="canvasClass" alt="Video Frame" /> -->
|
|
<canvas :ref="`videoEL${studentItem._id}`" class="canvasClass"></canvas>
|
|
</el-col>
|
|
<!-- <el-col :span="12">
|
|
<canvas ref="videoEL2" class="canvasClass"></canvas>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<canvas ref="videoEL3" class="canvasClass"></canvas>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<canvas ref="videoEL4" class="canvasClass"></canvas>
|
|
</el-col> -->
|
|
</el-row>
|
|
</el-col>
|
|
</el-row>
|
|
<el-dialog v-model="addDialog.show" :title="addDialog.title" width="80%" draggable
|
|
:close-on-click-modal="formDisabled">
|
|
<changeItem :formData="formData" :formDisabled="formDisabled" :type="addDialog.type" :formHeader="formHeader">
|
|
</changeItem>
|
|
<template #footer v-if="!formDisabled">
|
|
<div class="dialog-footer">
|
|
<el-button @click="closeDialog(addDialog)" type="info" plain>
|
|
取消
|
|
</el-button>
|
|
<el-button type="primary" @click="submitAdd" plain> 提交 </el-button>
|
|
</div>
|
|
</template>
|
|
</el-dialog>
|
|
</template>
|
|
<script>
|
|
import vueTable from "../component/table.vue";
|
|
import changeItem from "../component/changeItem.vue";
|
|
import _ from "lodash";
|
|
import {
|
|
addUser, //新增user信息
|
|
delUser, //删除user信息
|
|
updateUser, //修改user信息
|
|
getUser, //获取user信息
|
|
updatePassword,//修改密码
|
|
getzuobi,//获取作弊信息
|
|
} from "@/api/teacher";
|
|
import io from "socket.io-client";
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import * as echarts from 'echarts';
|
|
import getip from "@/plugins/getip"
|
|
export default {
|
|
name: "teacher",
|
|
components: { vueTable, changeItem },
|
|
data() {
|
|
return {
|
|
_: _,
|
|
refName: "userTable",
|
|
localComponent: [
|
|
{
|
|
prop: "xuexiaomingcheng",
|
|
label: "School_name",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "xuexiaodaihao",
|
|
label: "学校代号",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "zhuanyemingcheng",
|
|
label: "专业名称",
|
|
type: "text",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "zhuanyedaihao",
|
|
label: "专业代号",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "nianji",
|
|
label: "年级",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "banji",
|
|
label: "班级",
|
|
type: "text",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "xueshengxingming",
|
|
label: "学生姓名",
|
|
type: "text",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "xuehao",
|
|
label: "学号",
|
|
type: "text",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
label: "照片",
|
|
prop: "kaoshengtupian",
|
|
type: "img",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "mima",
|
|
label: "密码",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "chengji",
|
|
label: "成绩",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "zuobiqingkuang",
|
|
label: "作弊情况",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "kaoshileixing",
|
|
label: "考试类型",
|
|
type: "text",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "kaoshikemu",
|
|
label: "考试科目",
|
|
type: "text",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "kaoshilianjie",
|
|
label: "考试链接",
|
|
type: "link",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "kaoshishijianduan",
|
|
label: "考试时间段",
|
|
type: "datetimerange",
|
|
startplaceholder: "考试开始时间",
|
|
endplaceholder: "考试结束时间",
|
|
rangeseparator: "-",
|
|
tableShow: true,
|
|
formShow: true
|
|
},
|
|
{
|
|
prop: "chuangjianshijian",
|
|
label: "创建时间",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: false
|
|
},
|
|
{
|
|
prop: "gengxinshijian",
|
|
label: "更新时间",
|
|
type: "text",
|
|
tableShow: false,
|
|
formShow: false
|
|
},
|
|
],
|
|
tableHeader: [],
|
|
tableData: [],
|
|
addDialog: {
|
|
type: "add",
|
|
show: false,
|
|
title: "新增用户",
|
|
},
|
|
formData: {},
|
|
formDataLocal: {},
|
|
formHeader: [],
|
|
formHeaderLocal: [],
|
|
formDisabled: false,
|
|
socket: null,
|
|
videoStream: null,
|
|
imageSrc: "",
|
|
canvasObj: {},
|
|
zuobiInterval: null,
|
|
teacherEcharts: null,
|
|
};
|
|
},
|
|
watch: {},
|
|
computed: {},
|
|
async mounted() {
|
|
if (localStorage.getItem("userId")) {
|
|
this.dealTableHeader()
|
|
this.dealFormHeader()
|
|
await this.init();
|
|
await this.initWebSocket()
|
|
await this.initCanvas()
|
|
let echartsDom = document.getElementById('teacherZuobiEcharts');
|
|
this.teacherEcharts = echarts.init(echartsDom)
|
|
this.zuobiInterval = setInterval(async () => {
|
|
await this.getzuobiList()
|
|
}, 1000 * 10);
|
|
// getip((ip) => { console.log(777, ip) })
|
|
}
|
|
},
|
|
methods: {
|
|
// 处理表格
|
|
dealTableHeader() {
|
|
this.tableHeader = _.filter(this.localComponent, o => {
|
|
o.minWidth = o.label.length * 17 + 5
|
|
return o.tableShow
|
|
})
|
|
},
|
|
// 处理表头
|
|
dealFormHeader() {
|
|
this.formHeaderLocal = []
|
|
this.formDataLocal = {}
|
|
for (let i = 0; i < this.localComponent.length; i++) {
|
|
let element = this.localComponent[i];
|
|
if (element.formShow) {
|
|
this.formHeaderLocal.push({ ...element })
|
|
if (["text"].indexOf(element.type) !== -1) {
|
|
this.formDataLocal[element.prop] = ""
|
|
} else if (["datetimerange"].indexOf(element.type) !== -1) {
|
|
this.formDataLocal[element.prop] = []
|
|
} else if (["img"].indexOf(element.type) !== -1) {
|
|
this.formDataLocal[element.prop] = ""
|
|
}
|
|
}
|
|
}
|
|
},
|
|
// 初始化表格
|
|
async init(params = {}) {
|
|
let res = await getUser({
|
|
...params,
|
|
});
|
|
this.tableData = _.filter(res.list, o => o.isAdmin === 0);
|
|
},
|
|
// 打开新增学生的模态框
|
|
async addUserData() {
|
|
this.addDialog = {
|
|
type: "add",
|
|
show: true,
|
|
title: "新增学生",
|
|
};
|
|
this.formData = { ...this.formDataLocal };
|
|
this.formHeader = _.cloneDeep(this.formHeaderLocal)
|
|
this.formDisabled = false;
|
|
},
|
|
// 关闭模态框
|
|
closeDialog(addDialog) {
|
|
addDialog.show = false;
|
|
},
|
|
// 新增学生和编辑学生的提交
|
|
async submitAdd() {
|
|
let subData = {};
|
|
for (let i = 0; i < this.formHeader.length; i++) {
|
|
let elei = this.formHeader[i];
|
|
if (elei.type !== "time") {
|
|
subData[elei.prop] = this.formData[elei.prop];
|
|
}
|
|
}
|
|
if (this.addDialog.type === "edit") {
|
|
subData["userid"] = this.formData._id;
|
|
}
|
|
let res = {}
|
|
try {
|
|
if (this.addDialog.type === "edit") {
|
|
res = await updateUser(subData);
|
|
} else {
|
|
res = await addUser(subData);
|
|
}
|
|
ElMessage({
|
|
message: res.msg,
|
|
type: "success",
|
|
});
|
|
} catch (error) {
|
|
} finally {
|
|
this.closeDialog(this.addDialog);
|
|
await this.init();
|
|
}
|
|
},
|
|
// 删除学生信息
|
|
async delUserData() {
|
|
let rowList =
|
|
this.$refs[`${this.refName}Parent`].$refs[
|
|
this.refName
|
|
].getSelectionRows();
|
|
let delData = [];
|
|
let delDispalyData = [];
|
|
for (let i = 0; i < rowList.length; i++) {
|
|
let elei = rowList[i];
|
|
delData.push(elei._id);
|
|
delDispalyData.push(`${elei.xueshengxingming}`);
|
|
}
|
|
ElMessageBox.confirm(`${delDispalyData.join("、")}`, "删除", {
|
|
confirmButtonText: "确认",
|
|
cancelButtonText: "取消",
|
|
type: "error",
|
|
}).then(async () => {
|
|
let res = await delUser({ ids: delData });
|
|
await this.init();
|
|
ElMessage({
|
|
message: res.msg,
|
|
type: "success",
|
|
});
|
|
});
|
|
},
|
|
// 查看学生
|
|
detailInfo(item) {
|
|
this.addDialog = {
|
|
type: "info",
|
|
show: true,
|
|
title: `查看${item.name || ""}`,
|
|
};
|
|
this.formDisabled = true;
|
|
this.formHeader = _.cloneDeep(_.filter(this.formHeaderLocal, o => o.prop !== "mima"))
|
|
this.formData = { ...item };
|
|
},
|
|
// 编辑学生
|
|
editInfo(item) {
|
|
this.addDialog = {
|
|
type: "edit",
|
|
show: true,
|
|
title: `编辑${item.name || ""}`,
|
|
};
|
|
this.formDisabled = false;
|
|
this.formHeader = _.cloneDeep(_.filter(this.formHeaderLocal, o => o.prop !== "mima"))
|
|
this.formData = { ...item };
|
|
},
|
|
//更新密码
|
|
updateMima(item) {
|
|
ElMessageBox.prompt("请输入新密码", "修改密码", {
|
|
confirmButtonText: "确认",
|
|
cancelButtonText: "取消",
|
|
}).then(async (value) => {
|
|
let res = await updatePassword({ userid: item._id, mima: value.value });
|
|
ElMessage({
|
|
message: res.msg,
|
|
type: "success",
|
|
});
|
|
})
|
|
},
|
|
// 初始化画布
|
|
async initCanvas() {
|
|
for (let i = 0; i < this.tableData.length; i++) {
|
|
let element = this.tableData[i];
|
|
if (this.$refs[`videoEL${element._id}`]) {
|
|
this.canvasObj[`context${element._id}`] = this.$refs[`videoEL${element._id}`][0].getContext('2d');
|
|
}
|
|
}
|
|
},
|
|
// 初始化实时传输信息
|
|
async initWebSocket() {
|
|
this.socket = io('http://127.0.0.1:5000',
|
|
{
|
|
secure: true,
|
|
rejectUnauthorized: false, // 由于自签名证书,可能需要设置此选项
|
|
query: { id: localStorage.getItem('userId'), isAdmin: "1" }
|
|
}
|
|
); // 替换为你的后端服务器地址
|
|
this.socket.on('connect', () => {
|
|
console.log('Connected to server');
|
|
console.log(879784, this.socket.id)
|
|
});
|
|
//监听视频帧并渲染
|
|
for (let index = 0; index < this.tableData.length; index++) {
|
|
let element = this.tableData[index];
|
|
this.socket.on(`teacherVideo${element._id}`, (data) => {
|
|
// 在这里处理接收到的视频帧
|
|
let blob = this.dataURLToBlob(data.data);
|
|
let urlCreator = window.URL || window.webkitURL;
|
|
let imageUrl = urlCreator.createObjectURL(blob);
|
|
let image = new Image();
|
|
image.onload = () => {
|
|
this.canvasObj[`context${data.userId}`].drawImage(image, 0, 0, image.width, image.height, 0, 0, 300, 150);
|
|
};
|
|
image.src = imageUrl;
|
|
});
|
|
|
|
}
|
|
//监听说话信息并提示
|
|
for (let index = 0; index < this.tableData.length; index++) {
|
|
let element = this.tableData[index];
|
|
this.socket.on(`teacherTalk${element._id}`, (data) => {
|
|
let userId = _.get(data, ['userId'], "")
|
|
let msg = _.get(data, ['data'], "")
|
|
let type = _.get(data, ['type'], "")
|
|
let stedentItem = _.find(this.tableData, o => o._id === userId)
|
|
if (type === "talk") {
|
|
ElMessage({
|
|
message: `${stedentItem.xueshengxingming}说:${msg}`,
|
|
type: "error",
|
|
});
|
|
} else {
|
|
if (type === "normal") {
|
|
ElMessage({
|
|
message: `${stedentItem.xueshengxingming}${type}`,
|
|
type: "success",
|
|
});
|
|
} else {
|
|
ElMessage({
|
|
message: `${stedentItem.xueshengxingming}正在:${type}`,
|
|
type: "error",
|
|
});
|
|
}
|
|
|
|
|
|
}
|
|
console.log(78744, stedentItem, msg)
|
|
// 在这里处理接收到的学生端谈话信息
|
|
});
|
|
}
|
|
//监听疑问信息并处理
|
|
for (let index = 0; index < this.tableData.length; index++) {
|
|
let element = this.tableData[index];
|
|
this.socket.on(`teacherMsg${element._id}`, (data) => {
|
|
let userId = _.get(data, ['userId'], "")
|
|
let msg = _.get(data, ['data'], "")
|
|
let stedentItem = _.find(this.tableData, o => o._id === userId)
|
|
ElMessageBox.prompt(`${stedentItem.xueshengxingming}问:${msg}`, `疑问`, {
|
|
confirmButtonText: 'OK',
|
|
cancelButtonText: 'Cancel'
|
|
})
|
|
.then(({ value }) => {
|
|
ElMessage({
|
|
type: 'success',
|
|
message: `已回答${value}`,
|
|
})
|
|
this.socket.emit('answerMsg', { userId: stedentItem._id, data: `${msg}@@@${value}` });
|
|
})
|
|
.catch(() => {
|
|
ElMessage({
|
|
type: 'info',
|
|
message: 'Input canceled',
|
|
})
|
|
this.socket.emit('answerMsg', { userId: stedentItem._id, data: `${msg}@@@ ` });
|
|
})
|
|
// 在这里处理接收到的学生端疑问信息
|
|
});
|
|
}
|
|
},
|
|
stopVideoStream() {
|
|
if (this.videoStream) {
|
|
this.videoStream.getTracks().forEach(track => track.stop());
|
|
}
|
|
},
|
|
closeWebSocket() {
|
|
if (this.socket && this.socket.connected) {
|
|
this.socket.disconnect();
|
|
}
|
|
},
|
|
dataURLToBlob(dataURL) {
|
|
let parts = dataURL.split(';base64,');
|
|
let contentType = parts[0].split(':')[1];
|
|
let byteCharacters = atob(parts[1]);
|
|
let byteArrays = [];
|
|
|
|
for (let offset = 0; offset < byteCharacters.length; offset += 1024) {
|
|
let slice = byteCharacters.slice(offset, offset + 1024);
|
|
let byteNumbers = new Array(slice.length);
|
|
for (let i = 0; i < slice.length; i++) {
|
|
byteNumbers[i] = slice.charCodeAt(i);
|
|
}
|
|
let byteArray = new Uint8Array(byteNumbers);
|
|
byteArrays.push(byteArray);
|
|
}
|
|
let blob = new Blob(byteArrays, { type: contentType });
|
|
return blob;
|
|
},
|
|
// 获取作弊信息
|
|
async getzuobiList() {
|
|
let res = await getzuobi()
|
|
let zuobiList = res.list
|
|
let zuobiObj = _.groupBy(zuobiList, 'userId')
|
|
let allZuobi = {}
|
|
let dataLocal = []
|
|
for (let key in zuobiObj) {
|
|
let value = zuobiObj[key]
|
|
let valueObj = _.groupBy(value, 'type')
|
|
let studentItem = _.find(this.tableData, o => o._id === key)
|
|
if (studentItem) {
|
|
allZuobi[key] = valueObj
|
|
dataLocal.push({
|
|
value: value.length,
|
|
name: studentItem.xueshengxingming,
|
|
_id: key
|
|
})
|
|
}
|
|
}
|
|
let option = {
|
|
tooltip: {
|
|
trigger: 'item',
|
|
formatter: function (params) {
|
|
let msg = ""
|
|
for (let key1 in allZuobi[params.data._id]) {
|
|
let value1 = allZuobi[params.data._id][key1]
|
|
msg += `${key1}:${value1.length}<br>`
|
|
}
|
|
return `${params.name}<br>${msg}`;
|
|
}
|
|
},
|
|
legend: {
|
|
top: '1%',
|
|
left: 'center'
|
|
},
|
|
series: [
|
|
{
|
|
name: '作弊次数',
|
|
type: 'pie',
|
|
radius: ['30%', '80%'],
|
|
avoidLabelOverlap: false,
|
|
itemStyle: {
|
|
borderRadius: 10,
|
|
borderColor: '#fff',
|
|
borderWidth: 2
|
|
},
|
|
label: {
|
|
show: false,
|
|
position: 'center'
|
|
},
|
|
emphasis: {
|
|
label: {
|
|
show: true,
|
|
fontSize: 40,
|
|
fontWeight: 'bold'
|
|
}
|
|
},
|
|
labelLine: {
|
|
show: false
|
|
},
|
|
data: dataLocal
|
|
}
|
|
]
|
|
};
|
|
this.teacherEcharts.setOption(option)
|
|
}
|
|
},
|
|
beforeUnmount() {
|
|
this.stopVideoStream();
|
|
this.closeWebSocket();
|
|
clearInterval(this.zuobiInterval)
|
|
},
|
|
};
|
|
</script>
|
|
<style scoped>
|
|
.canvasClass {
|
|
position: relative;
|
|
width: 270px;
|
|
height: 270px;
|
|
background-color: aliceblue
|
|
}
|
|
|
|
#teacherZuobiEcharts {
|
|
width: 300px;
|
|
height: 300px;
|
|
position: relative;
|
|
left: calc(50% - 200px);
|
|
}
|
|
</style>
|
|
|