baigl #180
|
@ -10,6 +10,7 @@ module.exports = {
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/require-default-prop': 'off',
|
'vue/require-default-prop': 'off',
|
||||||
'vue/multi-word-component-names': 'off'
|
'vue/multi-word-component-names': 'off',
|
||||||
|
'prettier/prettier': 'off'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export default defineConfig({
|
||||||
proxy: {
|
proxy: {
|
||||||
'/dev-api': {
|
'/dev-api': {
|
||||||
target: 'http://27.128.240.72:7865',
|
target: 'http://27.128.240.72:7865',
|
||||||
|
// target: 'http://36.134.181.164:7863',
|
||||||
// target: 'http://192.168.2.52:7863',
|
// target: 'http://192.168.2.52:7863',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
"@electron/remote": "^2.1.2",
|
"@electron/remote": "^2.1.2",
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
||||||
|
"@vue-office/docx": "^1.6.2",
|
||||||
|
"@vue-office/excel": "^1.7.11",
|
||||||
|
"@vue-office/pdf": "^2.0.2",
|
||||||
"@vueuse/core": "^10.11.0",
|
"@vueuse/core": "^10.11.0",
|
||||||
"cropperjs": "^1.6.2",
|
"cropperjs": "^1.6.2",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
// 查询evaluation列表
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询反馈列表
|
||||||
|
|
||||||
|
// 查询classworkdata列表 班级作业列表
|
||||||
|
export function listClassworkdata(query) {
|
||||||
|
return request({
|
||||||
|
url: '/education/classworkdata/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询entpcoursework列表 课程作业列表
|
||||||
|
export function listEntpcoursework(query) {
|
||||||
|
return request({
|
||||||
|
url: '/education/entpcoursework/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询classworkeval列表 课堂作业列表
|
||||||
|
export function listClassworkeval(query) {
|
||||||
|
return request({
|
||||||
|
url: '/education/classworkeval/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改classworkeval
|
||||||
|
export function updateClassworkeval(data) {
|
||||||
|
return request({
|
||||||
|
url: '/education/classworkeval',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改classworkdata
|
||||||
|
export function updateClassworkdata(data) {
|
||||||
|
return request({
|
||||||
|
url: '/education/classworkdata',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
<script setup name="ReFilePreview">
|
||||||
|
import "@vue-office/docx/lib/index.css";
|
||||||
|
import "@vue-office/excel/lib/index.css";
|
||||||
|
import { defineAsyncComponent, defineProps, onMounted } from "vue";
|
||||||
|
// import type { FileProps } from "@/components/RefilePreview/types";
|
||||||
|
import { useHooks } from "@/components/refile-preview/useReadFile";
|
||||||
|
|
||||||
|
|
||||||
|
import VueOfficeDocx from '@vue-office/docx'
|
||||||
|
import VueOfficeExcel from '@vue-office/excel'
|
||||||
|
import VueOfficePdf from '@vue-office/pdf'
|
||||||
|
|
||||||
|
// const VueOfficeDocx = defineAsyncComponent(() => import("@vue-office/docx"));
|
||||||
|
// const VueOfficeExcel = defineAsyncComponent(() => import("@vue-office/excel"));
|
||||||
|
// const VueOfficePdf = defineAsyncComponent(() => import("@vue-office/pdf"));
|
||||||
|
// const RePlayer = defineAsyncComponent(
|
||||||
|
// () => import("@/components/RePlayer/index.vue")
|
||||||
|
// );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
fileType: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
raw: () => new File([], ""),
|
||||||
|
filePath: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
textContent:{
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
excelOptions,
|
||||||
|
src,
|
||||||
|
filePreviewRef,
|
||||||
|
renderedHandler,
|
||||||
|
errorHandler,
|
||||||
|
renderTheFile,
|
||||||
|
isImage,
|
||||||
|
isVideo,
|
||||||
|
isText,
|
||||||
|
isAudio
|
||||||
|
} = useHooks(props);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
renderTheFile(); // 渲染文件
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
filePreviewRef
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="filePreviewRef" class="file-preview">
|
||||||
|
<vue-office-docx
|
||||||
|
v-if="props.fileType === 'docx' || props.fileType === 'doc'"
|
||||||
|
:src="props.filePath"
|
||||||
|
@rendered="renderedHandler"
|
||||||
|
@error="errorHandler"
|
||||||
|
/>
|
||||||
|
<vue-office-excel
|
||||||
|
v-if="props.fileType === 'xlsx' || props.fileType === 'xls'"
|
||||||
|
:src="props.filePath"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
:auto-resize="true"
|
||||||
|
:enable-scrollbars="true"
|
||||||
|
:options="excelOptions"
|
||||||
|
@rendered="renderedHandler"
|
||||||
|
@error="errorHandler"
|
||||||
|
/>
|
||||||
|
<vue-office-pdf
|
||||||
|
v-if="props.fileType === 'pdf'"
|
||||||
|
:src="props.filePath"
|
||||||
|
@rendered="renderedHandler"
|
||||||
|
@error="errorHandler"
|
||||||
|
/>
|
||||||
|
<el-image
|
||||||
|
v-if="isImage(props.fileType)"
|
||||||
|
:preview-teleported="true"
|
||||||
|
fit="cover"
|
||||||
|
class="w-[200px] align-left"
|
||||||
|
:src="props.filePath"
|
||||||
|
title="点击查看大图"
|
||||||
|
:preview-src-list="[src]"
|
||||||
|
/>
|
||||||
|
<video v-if="isVideo(props.fileType)" :src="props.filePath" style="width: 400px; height: 400px;" controls/>
|
||||||
|
<div v-if="isText(props.fileType)">
|
||||||
|
<pre v-html="props.textContent" />
|
||||||
|
</div>
|
||||||
|
<audio v-if="isAudio(props.fileType)" :src="props.filePath" controls />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.file-preview {
|
||||||
|
width: 90%;
|
||||||
|
height: 60vh;
|
||||||
|
overflow: auto;
|
||||||
|
text-align: left;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface FileProps {
|
||||||
|
id?: number;
|
||||||
|
type?: string;
|
||||||
|
fileType?: string;
|
||||||
|
raw?: File;
|
||||||
|
filePath?: string;
|
||||||
|
}
|
|
@ -0,0 +1,261 @@
|
||||||
|
import type { FileProps } from "@/components/refile-preview/types";
|
||||||
|
import { onUnmounted, ref } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export function useHooks(props: FileProps) {
|
||||||
|
const excelOptions = {
|
||||||
|
xls: props.fileType !== "xlsx", //预览xlsx文件设为false;预览xls文件设为true
|
||||||
|
minColLength: 0, // excel最少渲染多少列,如果想实现xlsx文件内容有几列,就渲染几列,可以将此值设置为0.
|
||||||
|
minRowLength: 0, // excel最少渲染多少行,如果想实现根据xlsx实际函数渲染,可以将此值设置为0.
|
||||||
|
widthOffset: 10, //如果渲染出来的结果感觉单元格宽度不够,可以在默认渲染的列表宽度上再加 Npx宽
|
||||||
|
heightOffset: 10, //在默认渲染的列表高度上再加 Npx高
|
||||||
|
beforeTransformData: workbookData => {
|
||||||
|
return workbookData;
|
||||||
|
}, //底层通过exceljs获取excel文件内容,通过该钩子函数,可以对获取的excel文件内容进行修改,比如某个单元格的数据显示不正确,可以在此自行修改每个单元格的value值。
|
||||||
|
transformData: workbookData => {
|
||||||
|
return workbookData;
|
||||||
|
} //将获取到的excel数据进行处理之后且渲染到页面之前,可通过transformData对即将渲染的数据及样式进行修改,此时每个单元格的text值就是即将渲染到页面上的内容
|
||||||
|
};
|
||||||
|
const src = ref();
|
||||||
|
const filePreviewRef = ref();
|
||||||
|
/**
|
||||||
|
* 渲染完成
|
||||||
|
*/
|
||||||
|
const renderedHandler = () => {
|
||||||
|
console.log("渲染完成");
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 渲染失败
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
const errorHandler = e => {
|
||||||
|
console.log("渲染失败", e);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染文件
|
||||||
|
*/
|
||||||
|
async function renderTheFile() {
|
||||||
|
if (props.type === "local") {
|
||||||
|
console.log("本地文件" + props.fileType);
|
||||||
|
isImage(props.fileType) && localImagePreview(props.raw);
|
||||||
|
isDoc(props.fileType) && localOfficePreview(props.raw);
|
||||||
|
isText(props.fileType) && localTextPreview(props.raw);
|
||||||
|
isVideo(props.fileType) && localVideoPreview(props.raw);
|
||||||
|
isAudio(props.fileType) && localAudioPreview(props.raw);
|
||||||
|
} else {
|
||||||
|
if (isVideo(props.fileType)) {
|
||||||
|
src.value =
|
||||||
|
import.meta.env.VITE_APP_BASE_URL +
|
||||||
|
"/upload/attachments/getTeamOfVideo?id=" +
|
||||||
|
props.id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isText(props.fileType)) {
|
||||||
|
const response = await axios.get(
|
||||||
|
import.meta.env.VITE_STATIC_URL + props.filePath,
|
||||||
|
{
|
||||||
|
responseType: "text"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
src.value = response.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
src.value = import.meta.env.VITE_STATIC_URL + props.filePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验图片类型
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
function isImage(type: string) {
|
||||||
|
const types = [
|
||||||
|
"jpg",
|
||||||
|
"png",
|
||||||
|
"gif",
|
||||||
|
"jpeg",
|
||||||
|
"bmp",
|
||||||
|
"webp",
|
||||||
|
"svg",
|
||||||
|
"tiff",
|
||||||
|
"tif",
|
||||||
|
"jpeg",
|
||||||
|
"jfif",
|
||||||
|
"pjpeg",
|
||||||
|
"pjp"
|
||||||
|
];
|
||||||
|
return types.includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验文档类型
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
function isDoc(type: string) {
|
||||||
|
const types = ["docx", "doc", "xlsx", "xls", "pdf"];
|
||||||
|
return types.includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验文本类型
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
function isText(type: string) {
|
||||||
|
const types = [
|
||||||
|
"txt",
|
||||||
|
"md",
|
||||||
|
"log",
|
||||||
|
"json",
|
||||||
|
"xml",
|
||||||
|
"html",
|
||||||
|
"css",
|
||||||
|
"js",
|
||||||
|
"java",
|
||||||
|
"c",
|
||||||
|
"cpp",
|
||||||
|
"h",
|
||||||
|
"hpp",
|
||||||
|
"py",
|
||||||
|
"rb",
|
||||||
|
"go",
|
||||||
|
"sh",
|
||||||
|
"bat",
|
||||||
|
"ps1",
|
||||||
|
"psm1",
|
||||||
|
"ps1xml",
|
||||||
|
"psc1",
|
||||||
|
"psd1",
|
||||||
|
"psm1",
|
||||||
|
"ps1xml",
|
||||||
|
"psc1",
|
||||||
|
"psd1",
|
||||||
|
"ps1xml",
|
||||||
|
"psc1",
|
||||||
|
"ps1xml",
|
||||||
|
"psc1",
|
||||||
|
"psd1"
|
||||||
|
];
|
||||||
|
return types.includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检验视频格式
|
||||||
|
function isVideo(type: string) {
|
||||||
|
const types = [
|
||||||
|
"mp4",
|
||||||
|
"avi",
|
||||||
|
"rmvb",
|
||||||
|
"mkv",
|
||||||
|
"flv",
|
||||||
|
"wmv",
|
||||||
|
"mov",
|
||||||
|
"webm",
|
||||||
|
"m4v",
|
||||||
|
"mpg",
|
||||||
|
"mpeg",
|
||||||
|
"3gp",
|
||||||
|
"3g2",
|
||||||
|
"vob",
|
||||||
|
"ogv",
|
||||||
|
"ogg",
|
||||||
|
"mts",
|
||||||
|
"m2ts",
|
||||||
|
"ts",
|
||||||
|
"m2v",
|
||||||
|
"mpe",
|
||||||
|
"mpv",
|
||||||
|
"m4p",
|
||||||
|
"m4v",
|
||||||
|
"mpv2",
|
||||||
|
"m4v",
|
||||||
|
"m4p",
|
||||||
|
"m4v",
|
||||||
|
"m4p"
|
||||||
|
];
|
||||||
|
return types.includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检验音频文件
|
||||||
|
function isAudio(type: string) {
|
||||||
|
const types = ["mp3", "wav", "ogg", "flac", "aac", "wma", "m4a", "wma"];
|
||||||
|
return types.includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览本地Office文件
|
||||||
|
* @param file
|
||||||
|
*/
|
||||||
|
function localOfficePreview(file: File) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
reader.onload = loadEvent => {
|
||||||
|
const arrayBuffer = loadEvent.target.result;
|
||||||
|
const blob = new Blob([arrayBuffer], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
|
});
|
||||||
|
src.value = URL.createObjectURL(blob);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览本地图片
|
||||||
|
* @param file
|
||||||
|
*/
|
||||||
|
function localImagePreview(file: File) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
src.value = e.target.result;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览本地文本
|
||||||
|
* @param file
|
||||||
|
*/
|
||||||
|
function localTextPreview(file: File) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
src.value = e.target.result;
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览本地视频
|
||||||
|
* @param file
|
||||||
|
*/
|
||||||
|
function localVideoPreview(file: File) {
|
||||||
|
src.value = URL.createObjectURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
function localAudioPreview(file: File) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
const blob = new Blob([reader.result], { type: "audio/mpeg" });
|
||||||
|
src.value = URL.createObjectURL(blob);
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理工作:当组件卸载时释放URL对象
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (src.value) {
|
||||||
|
isVideo(props.fileType) && URL.revokeObjectURL(src.value);
|
||||||
|
isAudio(props.fileType) && URL.revokeObjectURL(src.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
excelOptions,
|
||||||
|
src,
|
||||||
|
filePreviewRef,
|
||||||
|
isImage,
|
||||||
|
renderedHandler,
|
||||||
|
errorHandler,
|
||||||
|
renderTheFile,
|
||||||
|
isVideo,
|
||||||
|
isText,
|
||||||
|
isAudio
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,514 @@
|
||||||
|
export const isJson = (str) => {
|
||||||
|
if (typeof str == 'string') {
|
||||||
|
try {
|
||||||
|
let obj = JSON.parse(str)
|
||||||
|
if (typeof obj == 'object' && obj) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description processList 格式化试题
|
||||||
|
* @param {*} row
|
||||||
|
*/
|
||||||
|
export const processList = (row) => {
|
||||||
|
for (var i = 0; i < row.length; i++) {
|
||||||
|
if (isJson(row[i].workanalysis)) {
|
||||||
|
//1、先默认格式化 格式化各项内容(待优化, 后续界面显示的为format的值)
|
||||||
|
row[i].titleFormat = row[i].title // 题目
|
||||||
|
row[i].examdateFormat = row[i].examdate // ?考试日期 eg: 2024-07-11 14:39:27"
|
||||||
|
row[i].workdescFormat = row[i].workdesc // 题目选项
|
||||||
|
row[i].workanswerFormat = row[i].workanswer // 题目正确答案
|
||||||
|
if (row[i].workanswerFormat == null || row[i].workanswerFormat == '') {
|
||||||
|
row[i].workanswerFormat = '见试题解答内容'
|
||||||
|
}
|
||||||
|
|
||||||
|
// workanalysis 解析内容(analyse:解答; method:分析; discuss:点评; )
|
||||||
|
var jjj = JSON.parse(row[i].workanalysis)
|
||||||
|
row[i].analyse = jjj.analyse
|
||||||
|
row[i].method = jjj.method
|
||||||
|
row[i].discuss = jjj.discuss
|
||||||
|
//row[i].discusscollapse = false;
|
||||||
|
if (row[i].examdate !== null && row[i].examdate !== undefined) {
|
||||||
|
row[i].examdate = row[i].examdate.substring(0, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 具体题型数据结构处理
|
||||||
|
if (row[i].worktype == '复合题') {
|
||||||
|
// 旧类型
|
||||||
|
if (row[i].title.indexOf('!@#$%') !== -1) {
|
||||||
|
// 1.选项解析替换
|
||||||
|
const options = JSON.parse(row[i].workdesc)
|
||||||
|
// 题目(背景材料+复合题目)
|
||||||
|
const bjTitle = row[i].title.split('!@#$%')[0]
|
||||||
|
const tmTitles = row[i].title.split('!@#$%').filter((it, ix) => ix > 0)
|
||||||
|
// console.log(bjTitle,'背景标题');
|
||||||
|
// console.log(tmTitles,'复合题目');
|
||||||
|
let titls = []
|
||||||
|
options.forEach((element, index1) => {
|
||||||
|
const workDescArr = element.split('#&')
|
||||||
|
let tmp = ''
|
||||||
|
let j = 0
|
||||||
|
for (; j < workDescArr.length; j++) {
|
||||||
|
if (j % 2 == 0) {
|
||||||
|
tmp += `<div style='width:80%;display:flex;'>`
|
||||||
|
}
|
||||||
|
const char = String.fromCharCode(65 + j)
|
||||||
|
tmp += `<div style='display:flex;margin-left:2%;width:35%;overflow:hidden;text-overflow:ellipsis;font-size:0.9em;'>${char}.${workDescArr[j]}</div>`
|
||||||
|
if (j % 2 == 1) {
|
||||||
|
tmp += '</div>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// j此刻已自增1, 故当选项为单数时, 需要补充结束标签
|
||||||
|
if (j % 2 == 1) {
|
||||||
|
tmp += '</div>'
|
||||||
|
}
|
||||||
|
|
||||||
|
// workDescArr为 [''] 表示为 判断题或者填空题,这里不需要选项
|
||||||
|
if (workDescArr[0] != '') {
|
||||||
|
titls.splice(index1, 1, tmp)
|
||||||
|
} else {
|
||||||
|
titls.splice(index1, 1, '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const s = []
|
||||||
|
tmTitles.map((it, ix) => {
|
||||||
|
s.push(it)
|
||||||
|
titls.map((it2, ix2) => {
|
||||||
|
if (ix == ix2) {
|
||||||
|
s.push(it2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// console.log(s,'?????????????????')
|
||||||
|
|
||||||
|
row[i].titleFormat = bjTitle + s.join('')
|
||||||
|
row[i].workdescFormat = ''
|
||||||
|
|
||||||
|
//2.答案 - 数字转为ABCD
|
||||||
|
const answerArr = JSON.parse(row[i].workanswer)
|
||||||
|
let indexLabel = 1
|
||||||
|
let arr = []
|
||||||
|
answerArr.forEach((item) => {
|
||||||
|
const arrTmp = item.answer.split('#&')
|
||||||
|
let value = `(${indexLabel})`
|
||||||
|
arrTmp.forEach((element, i) => {
|
||||||
|
if (item.type == '单选题' || item.type == '多选题') {
|
||||||
|
value += `${String.fromCharCode(65 + Number(element))}`
|
||||||
|
}
|
||||||
|
if (item.type == '判断题' || item.type == '填空题') {
|
||||||
|
// 去除下 html标签
|
||||||
|
value += `${element.replace(/<[^>]+>/g, '')}` + (i == arrTmp.length - 1 ? '' : '、')
|
||||||
|
}
|
||||||
|
if (item.type == '主观题') {
|
||||||
|
if (element) {
|
||||||
|
console.log(element, 'element')
|
||||||
|
value += item.answer
|
||||||
|
} else {
|
||||||
|
value += '答案不唯一,请参考分析解答点评!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
arr.push(value)
|
||||||
|
indexLabel++
|
||||||
|
})
|
||||||
|
const answer = arr.join('<br />')
|
||||||
|
|
||||||
|
row[i].workanswerFormat = answer
|
||||||
|
} else {
|
||||||
|
// 处理[题干显示] - 不再需要处理
|
||||||
|
// row[i].titleFormat = row[i].title; // 仅占位提示
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理[选项显示] - 特殊结构
|
||||||
|
* [
|
||||||
|
* {type: '单选题', title: '题目1', options: ['ABC123','ABC123']},
|
||||||
|
* {type: '多选题', title: '题目1', options: ['ABC123','ABC123']},
|
||||||
|
* {type: '填空题', title: '题目1', options: []},
|
||||||
|
* {type: '判断题', title: '题目1', options: []},
|
||||||
|
* {type: '主观题', title: '题目1', options: []},
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
let workDescArr = JSON.parse(row[i].workdesc)
|
||||||
|
let workDescHtml = `<div style='width:80%;display:flex;>`
|
||||||
|
workDescArr.map((item, index) => {
|
||||||
|
if (item.type == '单选题' || item.type == '多选题') {
|
||||||
|
workDescHtml += `<div style='width:80%;display:flex;'>${index + 1}. ${item.title}</div>`
|
||||||
|
let tmp = ''
|
||||||
|
let j = 0
|
||||||
|
let optionsArr = item.options
|
||||||
|
for (; j < optionsArr.length; j++) {
|
||||||
|
if (j % 2 == 0) {
|
||||||
|
tmp += `<div style='width:80%;display:flex;'>`
|
||||||
|
}
|
||||||
|
const char = String.fromCharCode(65 + j)
|
||||||
|
tmp += `<div style='display:flex;margin-left: 2%; width: 36%'>${char}.${optionsArr[j]}</div>`
|
||||||
|
if (j % 2 == 1) {
|
||||||
|
tmp += '</div>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// j此刻已自增1, 故当选项为单数时, 需要补充结束标签
|
||||||
|
if (j % 2 == 1) {
|
||||||
|
tmp += '</div>'
|
||||||
|
}
|
||||||
|
|
||||||
|
workDescHtml += tmp
|
||||||
|
} else if (item.type == '填空题' || item.type == '判断题' || item.type == '主观题') {
|
||||||
|
workDescHtml += `<div style='width:80%;display:flex;'>${index + 1}. ${item.title}</div>`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
workDescHtml += '</div>'
|
||||||
|
row[i].workdescFormat = workDescHtml
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理[答案显示] - 特殊结构
|
||||||
|
* [
|
||||||
|
* {type: '单选题', answer: ['0']},
|
||||||
|
* {type: '多选题', answer: ['0','1']},
|
||||||
|
* {type: '填空题', answer: ['填空1','填空2']},
|
||||||
|
* {type: '判断题', answer: ['0'/'1']},
|
||||||
|
* {type: '主观题', answer: [xxxx]},
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
let workAnswerArr = JSON.parse(row[i].workanswer)
|
||||||
|
let workAnswerHtml = ``
|
||||||
|
workAnswerArr.map((item, index) => {
|
||||||
|
const answerArr = item.answer //JSON.parse(item.answer);
|
||||||
|
if (item.type == '单选题' || item.type == '多选题') {
|
||||||
|
const answer = answerArr
|
||||||
|
.map((item) => {
|
||||||
|
return String.fromCharCode(65 + Number(item))
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}</div>`
|
||||||
|
} else if (item.type == '填空题') {
|
||||||
|
const answer = answerArr.join('、')
|
||||||
|
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}</div>`
|
||||||
|
} else if (item.type == '判断题') {
|
||||||
|
const answer = answerArr
|
||||||
|
.map((item) => {
|
||||||
|
return item === '1' ? '正确' : '错误'
|
||||||
|
})
|
||||||
|
.join('、')
|
||||||
|
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}</div>`
|
||||||
|
} else if (item.type == '主观题') {
|
||||||
|
// 复合题里面的主观题只有一个答案,或没填
|
||||||
|
const answer = answerArr.join('、')
|
||||||
|
if (answerArr[0]) {
|
||||||
|
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}</div>`
|
||||||
|
} else {
|
||||||
|
workAnswerHtml += `<div style='display:flex;'>${index + 1}. ${answer}答案不唯一,请参考分析解答点评!</div>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
row[i].workanswerFormat = workAnswerHtml
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
row[i].worktype == '主观题' ||
|
||||||
|
(row[i].worktype !== '单选题' &&
|
||||||
|
row[i].worktype !== '多选题' &&
|
||||||
|
row[i].worktype !== '填空题' &&
|
||||||
|
row[i].worktype !== '判断题')
|
||||||
|
) {
|
||||||
|
// 处理[选项显示] - 主观题中无选项, 故置空
|
||||||
|
row[i].workdescFormat = ''
|
||||||
|
row[i].workanswerFormat = ''
|
||||||
|
// 答案处理- eg: "\"不唯一的答案,参考\""
|
||||||
|
if (row[i].workanswer && row[i].workanswer != '') {
|
||||||
|
row[i].workanswerFormat = JSON.parse(row[i].workanswer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||||
|
// 通用选项结构 ['ABC123','ABC123'] | ['ABC123','ABC123'] | [](填空题无选项) | [](判断题无选项)
|
||||||
|
let workDescArr = []
|
||||||
|
if (
|
||||||
|
row[i].workdesc.charAt(0) === '[' &&
|
||||||
|
row[i].workdesc.charAt(row[i].workdesc.length - 1) === ']'
|
||||||
|
) {
|
||||||
|
//123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||||
|
workDescArr = JSON.parse(row[i].workdesc)
|
||||||
|
} else if (row[i].workdesc.indexOf('#&') !== -1) {
|
||||||
|
workDescArr = row[i].workdesc.split('#&')
|
||||||
|
} else if (row[i].workdesc.indexOf(',') !== -1) {
|
||||||
|
workDescArr = row[i].workdesc.split(',')
|
||||||
|
} else {
|
||||||
|
// 单字符串直接添加至空数组(待考虑确认)
|
||||||
|
workDescArr.push(row[i].workdesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单选题|多选题|填空题|判断题|主观题?(待确认是否归在这里)
|
||||||
|
// 通用答案结构 ['0'] | ['0','1'] | ['填空1','填空2'] | ['0'/'1']
|
||||||
|
let workAnswerArr = []
|
||||||
|
if (
|
||||||
|
row[i].workanswer.charAt(0) === '[' &&
|
||||||
|
row[i].workanswer.charAt(row[i].workanswer.length - 1) === ']'
|
||||||
|
) {
|
||||||
|
// 123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||||
|
workAnswerArr = JSON.parse(row[i].workanswer)
|
||||||
|
} else if (row[i].workanswer.indexOf('#&') !== -1) {
|
||||||
|
workAnswerArr = row[i].workanswer.split('#&')
|
||||||
|
} else if (row[i].workanswer.indexOf(',') !== -1) {
|
||||||
|
workAnswerArr = row[i].workanswer.split(',')
|
||||||
|
} else {
|
||||||
|
// 单字符串直接添加至空数组(待考虑确认)
|
||||||
|
workAnswerArr.push(row[i].workanswer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 具体题型处理
|
||||||
|
if (row[i].worktype == '单选题' || row[i].worktype == '多选题') {
|
||||||
|
// 处理[选项显示] - 拼接ABCD首序号
|
||||||
|
let tmp = ''
|
||||||
|
let j = 0
|
||||||
|
for (; j < workDescArr.length; j++) {
|
||||||
|
if (j % 2 == 0) {
|
||||||
|
tmp += `<div style='width:80%;display:flex;'>`
|
||||||
|
}
|
||||||
|
const char = String.fromCharCode(65 + j)
|
||||||
|
tmp += `<div style='display:flex;margin-left: 2%; width: 36%'>${char}.${workDescArr[j]}</div>`
|
||||||
|
if (j % 2 == 1) {
|
||||||
|
tmp += '</div>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (j % 2 == 0) {
|
||||||
|
tmp += '</div>'
|
||||||
|
}
|
||||||
|
row[i].workdescFormat = tmp
|
||||||
|
|
||||||
|
// 处理[答案显示] - 转换ABCD
|
||||||
|
let arr2Char = workAnswerArr
|
||||||
|
.map((item) => {
|
||||||
|
return String.fromCharCode(65 + Number(item))
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
row[i].workanswerFormat = arr2Char
|
||||||
|
} else if (row[i].worktype == '填空题') {
|
||||||
|
// 处理[选项显示] - 填空题中无选项, 故置空
|
||||||
|
row[i].workdescFormat = ''
|
||||||
|
|
||||||
|
// 处理[答案显示] - 逗号连接
|
||||||
|
row[i].workanswerFormat = workAnswerArr.join('、')
|
||||||
|
} else if (row[i].worktype == '判断题') {
|
||||||
|
// 处理[选项显示] - 判断题中无选项, 故置空
|
||||||
|
row[i].workdescFormat = ''
|
||||||
|
|
||||||
|
// 处理[答案显示] - 1-正常 0-错误
|
||||||
|
const answer = workAnswerArr
|
||||||
|
.map((item) => {
|
||||||
|
return item === '1' ? '正确' : '错误'
|
||||||
|
})
|
||||||
|
.join('、')
|
||||||
|
row[i].workanswerFormat = answer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
//2、处理单选题
|
||||||
|
if(row[i].worktype == '单选题' || row[i].worktype == '多选题' ){
|
||||||
|
//1.选项前增加ABCD workdesc: "①②#&①③#&②④#&③④" || "<div>为了活着</div>#&<div>为了填报肚子</div>#&<div>为了吃饭而吃饭</div>"
|
||||||
|
let workDescArr = [];
|
||||||
|
if(row[i].workdesc.indexOf('[')!==-1 && row[i].workdesc.indexOf(']')!==-1) {
|
||||||
|
//123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||||
|
workDescArr = JSON.parse(row[i].workdesc);
|
||||||
|
}
|
||||||
|
else if(row[i].workdesc.indexOf('#&')) {
|
||||||
|
workDescArr = row[i].workdesc.split('#&');
|
||||||
|
}
|
||||||
|
else if(row[i].workdesc.indexOf(',')){
|
||||||
|
workDescArr = row[i].workdesc.split(',');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 待考虑
|
||||||
|
workDescArr.push(item.workdesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//2.答案 - 数字转为ABCD
|
||||||
|
if(row[i].worktype == '单选题') {
|
||||||
|
const str2Char = String.fromCharCode(65+Number(row[i].workanswer));
|
||||||
|
row[i].workanswerFormat = str2Char;
|
||||||
|
} else if (row[i].worktype == '多选题') {
|
||||||
|
const answerArr = row[i].workanswer.split('#&');
|
||||||
|
let arr2Char = '';
|
||||||
|
for(let k=0; k<answerArr.length; k++){
|
||||||
|
arr2Char += String.fromCharCode(65+Number(answerArr[k]));
|
||||||
|
}
|
||||||
|
row[i].workanswerFormat = arr2Char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(row[i].worktype == '填空题') {
|
||||||
|
// console.log(row[i].workanswer.replace(/<[^>]*>/g, "").split('#&'),'????')
|
||||||
|
// 填空题答案
|
||||||
|
row[i].workanswerFormat = row[i].workanswer.replace(/#&/g,", ");
|
||||||
|
// 填空选项不需要展示,
|
||||||
|
row[i].workdescFormat = '';
|
||||||
|
}
|
||||||
|
else if(row[i].worktype == '判断题'){
|
||||||
|
// console.log(row[i].workanswer.replace(/<[^>]*>/g, "").split('#&'),'????')
|
||||||
|
// 判断题答案
|
||||||
|
row[i].workanswerFormat = row[i].workanswer.replace(/#&/g,", ");
|
||||||
|
// 判断选项不需要展示,
|
||||||
|
row[i].workdescFormat = '';
|
||||||
|
}
|
||||||
|
else if(row[i].worktype == '复合题') {
|
||||||
|
// 1.选项解析替换
|
||||||
|
const options = JSON.parse(row[i].workdesc);
|
||||||
|
// 题目(背景材料+复合题目)
|
||||||
|
const bjTitle = row[i].title.split('!@#$%')[0];
|
||||||
|
const tmTitles = row[i].title.split('!@#$%').filter((it,ix)=>ix>0);
|
||||||
|
// console.log(bjTitle,'背景标题');
|
||||||
|
// console.log(tmTitles,'复合题目');
|
||||||
|
let titls = [];
|
||||||
|
options.forEach((element,index1) => {
|
||||||
|
const workDescArr = element.split('#&');
|
||||||
|
let tmp = '';
|
||||||
|
let j=0;
|
||||||
|
for(; j<jsonArr.length; j++){
|
||||||
|
if(j%2 == 0){
|
||||||
|
tmp += `<div style='width:80%;display:flex;'>`;
|
||||||
|
}
|
||||||
|
const char = String.fromCharCode(65+j);
|
||||||
|
tmp += `<div style='display:flex;margin-left: 2%; width: 36%'>${char}.${jsonArr[j]}</div>`;
|
||||||
|
if(j%2 == 1){
|
||||||
|
tmp += '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(j%2== 0){
|
||||||
|
tmp += '</div>';
|
||||||
|
}
|
||||||
|
workdesc = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
row[i].workdescFormat = workdesc; // 题目选项
|
||||||
|
|
||||||
|
|
||||||
|
// 答案处理
|
||||||
|
let workanswer = '';
|
||||||
|
if(row[i].workanswer && row[i].workanswer != '') {
|
||||||
|
// 因答案内容存在多种格式: 1.["123","1234"] 2.123#&1234 3.123
|
||||||
|
if(row[i].workanswer.indexOf('[')!==-1 && row[i].workanswer.indexOf(']')!==-1) {
|
||||||
|
//123会直接被转换, 且不是数组对象, 故手动判断是否有[和]两个字符
|
||||||
|
let json = JSON.parse(row[i].workanswer);
|
||||||
|
// 单选、多选 需要 数字转为ABCD
|
||||||
|
if(row[i].worktype == '单选题') {
|
||||||
|
const str2Char = String.fromCharCode(65+Number(json[0]));
|
||||||
|
workanswer = str2Char;
|
||||||
|
} else if (row[i].worktype == '多选题') {
|
||||||
|
// const answerArr = row[i].workanswer.split('#&');
|
||||||
|
let arr2Char = '';
|
||||||
|
for(let k=0; k<json.length; k++){
|
||||||
|
arr2Char += String.fromCharCode(65+Number(json[k]));
|
||||||
|
}
|
||||||
|
workanswer = arr2Char;
|
||||||
|
} else if(row[i].worktype == '主观题' ) {
|
||||||
|
let arr2Char = '';
|
||||||
|
for(let k=0; k<json.length; k++){
|
||||||
|
const itemArr = json[k];
|
||||||
|
arr2Char += '('+ (parseInt(k) + 1) +')'+ itemArr.join('、')+ '<br />';
|
||||||
|
}
|
||||||
|
workanswer = arr2Char;
|
||||||
|
row[i].titleFormat = row[i].titleFormat.replace(/!@#\$%/g, '');
|
||||||
|
} else {
|
||||||
|
workanswer = json.join('、');
|
||||||
|
}
|
||||||
|
} else if(row[i].workanswer.indexOf('#&')) {
|
||||||
|
// 意味着多个答案或者填空内容
|
||||||
|
let workanswerList = row[i].workanswer.split('#&');
|
||||||
|
if(row[i].worktype == '多选题') {
|
||||||
|
// 数字转为ABCD
|
||||||
|
let arr2Char = '';
|
||||||
|
for(let k=0; k<workanswerList.length; k++){
|
||||||
|
arr2Char += String.fromCharCode(65+Number(workanswerList[k]));
|
||||||
|
}
|
||||||
|
workanswer = arr2Char;
|
||||||
|
}else{
|
||||||
|
workanswer = workanswerList.join('、');
|
||||||
|
}
|
||||||
|
} else if(row[i].workanswer.indexOf(',')){
|
||||||
|
// 意味这同样多个答案或者填空内容
|
||||||
|
let workanswerList = row[i].workanswer.split(',');
|
||||||
|
if(row[i].worktype == '多选题') {
|
||||||
|
// 数字转为ABCD
|
||||||
|
let arr2Char = '';
|
||||||
|
for(let k=0; k<workanswerList.length; k++){
|
||||||
|
arr2Char += String.fromCharCode(65+Number(workanswerList[k]));
|
||||||
|
}
|
||||||
|
workanswer = arr2Char;
|
||||||
|
}else{
|
||||||
|
workanswer = workanswerList.join('、');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 待考虑
|
||||||
|
workanswer = row[i].workanswer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row[i].workanswerFormat = workanswer; // 题目正确答案
|
||||||
|
|
||||||
|
|
||||||
|
//2.答案 - 数字转为ABCD
|
||||||
|
const answerArr = JSON.parse(row[i].workanswer);
|
||||||
|
let indexLabel = 1;
|
||||||
|
let arr = [];
|
||||||
|
answerArr.forEach(item => {
|
||||||
|
const arrTmp = item.answer.split('#&');
|
||||||
|
let value = `(${indexLabel})`;
|
||||||
|
arrTmp.forEach((element,i) => {
|
||||||
|
if(item.type == '单选题' || item.type == '多选题'){
|
||||||
|
value += `${String.fromCharCode(65+Number(element))}`;
|
||||||
|
}
|
||||||
|
if(item.type == '判断题' || item.type == '填空题'){
|
||||||
|
// 去除下 html标签
|
||||||
|
value += `${element.replace(/<[^>]+>/g, '')}`+ (i==arrTmp.length-1?'':'、');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
arr.push(value);
|
||||||
|
indexLabel++;
|
||||||
|
})
|
||||||
|
const answer = arr.join('<br />');
|
||||||
|
|
||||||
|
row[i].workanswerFormat = answer;
|
||||||
|
}
|
||||||
|
else if(row[i].worktype == '主观题') {
|
||||||
|
// 1.选项解析替换---主观题没选项
|
||||||
|
// 题目(背景材料+主观题目)
|
||||||
|
const bjTitle = row[i].title.split('!@#$%')[0];
|
||||||
|
const tmTitles = row[i].title.split('!@#$%').filter((it,ix)=>ix>0);
|
||||||
|
// console.log(bjTitle,'背景标题');
|
||||||
|
// console.log(tmTitles,'主观题目');
|
||||||
|
let titls = [];
|
||||||
|
const s = [];
|
||||||
|
tmTitles.map((it,ix)=>{
|
||||||
|
s.push(it);
|
||||||
|
})
|
||||||
|
// console.log(s,'?????????????????')
|
||||||
|
|
||||||
|
row[i].titleFormat = bjTitle + s.join('');
|
||||||
|
// 填空选项不需要展示,
|
||||||
|
row[i].workdescFormat = '';
|
||||||
|
|
||||||
|
//2.答案
|
||||||
|
// 填空题答案
|
||||||
|
const workanswerList = JSON.parse(row[i].workanswer);
|
||||||
|
let tmp='';
|
||||||
|
workanswerList&&workanswerList.map((item,index)=>{
|
||||||
|
tmp += '<div>'+(index+1)+'.'+item.replace(/#&/g, ',')+'</div>';
|
||||||
|
})
|
||||||
|
row[i].workanswerFormat = tmp;
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//处理答案
|
||||||
|
row[i].workanswerFormat = '见试题解答内容';
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,8 @@
|
||||||
|
/*
|
||||||
|
* @Author: 苦逼程序猿
|
||||||
|
* @Date: 2024-09-06 16:15:32
|
||||||
|
* @Warning: 千行代码,Bug露锋芒。
|
||||||
|
*/
|
||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
|
||||||
import Layout from '../layout/index.vue'
|
import Layout from '../layout/index.vue'
|
||||||
|
@ -63,6 +68,12 @@ export const constantRoutes = [
|
||||||
name: 'class',
|
name: 'class',
|
||||||
meta: {title: '班级中心'},
|
meta: {title: '班级中心'},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/classTask',
|
||||||
|
component: () => import('@/views/classTask/classTask.vue'),
|
||||||
|
name: 'class',
|
||||||
|
meta: {title: '作业批改'},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/examReport',
|
path: '/examReport',
|
||||||
component: () => import('@/views/examReport/index.vue'),
|
component: () => import('@/views/examReport/index.vue'),
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
const overviewStore = defineStore(
|
||||||
|
'overview',
|
||||||
|
{
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
tableList:[]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
getTableList(data){
|
||||||
|
this.tableList = [...data]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default overviewStore
|
||||||
|
|
||||||
|
|
|
@ -109,3 +109,28 @@ export const getAfterMinutes = (m) => {
|
||||||
minutes = minutes < 10 ? ('0' + minutes) : minutes
|
minutes = minutes < 10 ? ('0' + minutes) : minutes
|
||||||
return `${hours}:${minutes}`;
|
return `${hours}:${minutes}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格时间格式化
|
||||||
|
*/
|
||||||
|
export function formatDate(cellValue) {
|
||||||
|
if (cellValue == null || cellValue == "") return "";
|
||||||
|
var date = new Date(cellValue)
|
||||||
|
var year = date.getFullYear()
|
||||||
|
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
|
||||||
|
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
|
||||||
|
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
|
||||||
|
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
|
||||||
|
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
|
||||||
|
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
|
||||||
|
}
|
||||||
|
export function getTimeDate() {
|
||||||
|
var date = new Date()
|
||||||
|
var year = date.getFullYear()
|
||||||
|
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
|
||||||
|
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
|
||||||
|
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
|
||||||
|
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
|
||||||
|
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
|
||||||
|
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
|
||||||
|
}
|
|
@ -0,0 +1,413 @@
|
||||||
|
<!--
|
||||||
|
* @Author: 苦逼程序猿
|
||||||
|
* @Date: 2024-09-06 16:58:59
|
||||||
|
* @Warning: 千行代码,Bug露锋芒。
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<el-container class="class-reserv-wrap">
|
||||||
|
<div class="class-reserv-tabs">
|
||||||
|
<el-segmented v-model="tabActive" block :options="tabOptions" size="large" />
|
||||||
|
</div>
|
||||||
|
<div class="class-reserv-body">
|
||||||
|
<task-item
|
||||||
|
v-for="(item, index) in activeDataList"
|
||||||
|
v-show="tabActive === '进行中'"
|
||||||
|
:key="index"
|
||||||
|
:item="item"
|
||||||
|
:tabactive="tabActive"
|
||||||
|
@click="onClickItem(item)"
|
||||||
|
@delete-reserv="deleteReserv(item)"
|
||||||
|
></task-item>
|
||||||
|
<task-item
|
||||||
|
v-for="(item, index) in doneDataList"
|
||||||
|
v-show="tabActive === '已结束'"
|
||||||
|
:key="index"
|
||||||
|
:item="item"
|
||||||
|
:tabactive="tabActive"
|
||||||
|
@click="onClickItem(item)"
|
||||||
|
@delete-reserv="deleteReserv(item)"
|
||||||
|
></task-item>
|
||||||
|
</div>
|
||||||
|
<item-dialog ref="itemDialogRef" @cle-click="closeDialog"></item-dialog>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted, computed, watch, reactive } from 'vue'
|
||||||
|
import { getSelfReserv } from '@/api/classManage'
|
||||||
|
import { listClassmain } from '@/api/classManage/index'
|
||||||
|
import { listClassworkdata } from '@/api/classTask'
|
||||||
|
import { homeworklist } from '@/api/teaching/classwork'
|
||||||
|
|
||||||
|
import TaskItem from '@/views/classTask/container/task-item.vue'
|
||||||
|
import ItemDialog from '@/views/classTask/container/item-dialog.vue'
|
||||||
|
import { useToolState } from '@/store/modules/tool'
|
||||||
|
import { sessionStore } from '@/utils/tool'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
const userStore = useUserStore().user
|
||||||
|
const itemDialogRef = ref(null)
|
||||||
|
const tabOptions = ref(['进行中', '已结束'])
|
||||||
|
const tabActive = ref('进行中')
|
||||||
|
const dataList = ref([])
|
||||||
|
|
||||||
|
// 班级列表
|
||||||
|
const classList = ref([])
|
||||||
|
const classListIds = ref([])
|
||||||
|
// 所有班级作业列表
|
||||||
|
const classWorkList = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const activeDataList = computed(() => {
|
||||||
|
// return classWorkList.value
|
||||||
|
// 当前时间超过[作业任务]截止时间的跳过
|
||||||
|
return classWorkList.value && classWorkList.value.filter((item) => getDateTime > item.deaddate)
|
||||||
|
})
|
||||||
|
const deleteReserv = (item) => {
|
||||||
|
console.log('删除待开发', item)
|
||||||
|
// dataList.value = dataList.value.filter((is) => {
|
||||||
|
// return is.id !== item.id
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
const doneDataList = computed(() => {
|
||||||
|
// return classWorkList.value
|
||||||
|
return classWorkList.value && classWorkList.value.filter((item) => getDateTime < item.deaddate)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const getData = () => {
|
||||||
|
// 获取班级列表
|
||||||
|
listClassmain({ classuserid: userStore.userId, pageSize: 100, status: 'open' }).then((res) => {
|
||||||
|
var clslist = []
|
||||||
|
for (var i = 0; i < res.rows.length; i++) {
|
||||||
|
if (res.rows[i].classstudentlist != '') {
|
||||||
|
var array = JSON.parse('[' + res.rows[i].classstudentlist + ']')
|
||||||
|
res.rows[i].classstudents = array
|
||||||
|
}
|
||||||
|
classListIds.value.push(res.rows[i].id)
|
||||||
|
clslist.push(res.rows[i])
|
||||||
|
}
|
||||||
|
classList.value = clslist
|
||||||
|
|
||||||
|
// 班级作业数据,包含多个班级
|
||||||
|
homeworklist({
|
||||||
|
classidarray: classListIds.value.join(','),
|
||||||
|
//entpcourseid: '', // 章节id? 这里要全课程的作业 不分章节? 根据学段学科查询所有的作业
|
||||||
|
edustage: userStore.edustage,// 学段
|
||||||
|
edusubject: userStore.edusubject,//学科
|
||||||
|
orderby: 'uniquekey DESC',
|
||||||
|
pageSize: 100
|
||||||
|
}).then((response) => {
|
||||||
|
for (var i = 0; i < response.rows.length; i++) {
|
||||||
|
// 初始化部分新增字段值
|
||||||
|
response.rows[i].workdatalist = []
|
||||||
|
response.rows[i].workdatacount = 0 // 人数
|
||||||
|
response.rows[i].workdatalistVisible = false
|
||||||
|
response.rows[i].workdatafeedbackcount = 0 // 已交人数
|
||||||
|
response.rows[i].feedtimelength = 0
|
||||||
|
response.rows[i].rightAnswerCount = 0
|
||||||
|
response.rows[i].scoingRate = 0 + '%' // 得分率
|
||||||
|
response.rows[i].averagetime = 0 // 平均用时
|
||||||
|
|
||||||
|
// ----------------------------------------------
|
||||||
|
// 处理任务类型的UI
|
||||||
|
if (response.rows[i].worktype == '学习目标定位') {
|
||||||
|
response.rows[i].workclass = 'success'
|
||||||
|
response.rows[i].workcodesList = JSON.parse(response.rows[i].workcodes)
|
||||||
|
} else if (response.rows[i].worktype == '教材研读') {
|
||||||
|
response.rows[i].workclass = 'primary'
|
||||||
|
} else if (response.rows[i].worktype == '框架梳理') {
|
||||||
|
response.rows[i].workclass = 'warning'
|
||||||
|
} else if (response.rows[i].worktype == '学科定位') {
|
||||||
|
response.rows[i].workclass = 'info'
|
||||||
|
} else if (response.rows[i].worktype == '习题训练') {
|
||||||
|
response.rows[i].workclass = 'danger'
|
||||||
|
} else {
|
||||||
|
response.rows[i].workclass = ''
|
||||||
|
}
|
||||||
|
// 如果是习题训练任务,则检查一共有多少道
|
||||||
|
if (response.rows[i].entpcourseworklist != '') {
|
||||||
|
response.rows[i].entpcourseworklistarray = JSON.parse(
|
||||||
|
'[' + response.rows[i].entpcourseworklist + ']'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
response.rows[i].entpcourseworklistarray = []
|
||||||
|
}
|
||||||
|
// 根据 classworkdatastudentids 初始化判断分配的人数
|
||||||
|
if (
|
||||||
|
response.rows[i].classworkdatastudentids != '' &&
|
||||||
|
response.rows[i].classworkdatastudentids != null &&
|
||||||
|
response.rows[i].classworkdatastudentids != 'null'
|
||||||
|
) {
|
||||||
|
const stuList = JSON.parse('[' + response.rows[i].classworkdatastudentids + ']')
|
||||||
|
response.rows[i].workdatacount = stuList.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示分配人数(workdatacount)>0 的
|
||||||
|
if (response.rows && response.rows.length > 0) {
|
||||||
|
classWorkList.value = response.rows && response.rows.filter((item) => item.workdatacount > 0)
|
||||||
|
// classWorkList.value = response.rows && response.rows.filter((item) => item.workdatacount > 0 && item.uniquekey == '语文-0808-1')
|
||||||
|
//TODO: 这里没分页,貌似这个 total 不重要,后续看
|
||||||
|
total.value = response.total
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
|
||||||
|
// 再查找多个班级里,每个学生的作业数据
|
||||||
|
getStudentClassWorkData()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//--------------
|
||||||
|
// getSelfReserv().then((res) => {
|
||||||
|
// const list = res.data || []
|
||||||
|
// list.sort((a, b) => {
|
||||||
|
// if (a.status == '上课中') return -1
|
||||||
|
// else return 0
|
||||||
|
// })
|
||||||
|
// dataList.value = list
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
const toolStore = useToolState()
|
||||||
|
|
||||||
|
// 将标签中的双引号增加转义
|
||||||
|
const escapeHtmlQuotes = (str) => {
|
||||||
|
// 后端已replace双引号, 故前端不用在处理
|
||||||
|
return str
|
||||||
|
|
||||||
|
// 只对双引号进行转义
|
||||||
|
// return str.replace(/(<[^>]+>)/g, function (match) {
|
||||||
|
// return match.replace(/"/g, '\\"')
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
const pollingST = ref(null) //轮询定时器标识
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData() // 加载数据
|
||||||
|
// 轮询查询
|
||||||
|
getStudentClassWorkDataPolling()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 轮询查询 学生作业进度
|
||||||
|
const getStudentClassWorkDataPolling = () => {
|
||||||
|
// 先查询一次后,
|
||||||
|
getStudentVisible()
|
||||||
|
// 在轮询
|
||||||
|
pollingST.value = setInterval(() => {
|
||||||
|
getStudentVisible()
|
||||||
|
}, 1000 * 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
console.log('关闭弹窗,开启作业进度轮询')
|
||||||
|
getStudentClassWorkDataPolling()
|
||||||
|
}
|
||||||
|
const onClickItem = (item) => {
|
||||||
|
console.log('开启弹窗,关闭作业进度轮询')
|
||||||
|
clearInterval(pollingST.value)
|
||||||
|
itemDialogRef.value.openDialog(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(pollingST.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getDateTime = () => {
|
||||||
|
//获取当前时间
|
||||||
|
const now = new Date()
|
||||||
|
const year = now.getFullYear()
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(now.getDate()).padStart(2, '0')
|
||||||
|
const hh = String(now.getHours()).padStart(2, '0')
|
||||||
|
const mm = String(now.getMinutes()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day} ${hh}:${mm}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// [作业反馈] - 实际查询逻辑
|
||||||
|
const getStudentVisible = async () => {
|
||||||
|
if (classListIds.value.length <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 班级作业数据,多个班级
|
||||||
|
const response = await homeworklist({
|
||||||
|
classidarray: classListIds.value.join(','),
|
||||||
|
//entpcourseid: '', // 章节id? 这里要全课程的作业 不分章节? 根据学段学科查询所有的作业
|
||||||
|
edustage: userStore.edustage,// 学段
|
||||||
|
edusubject: userStore.edusubject,//学科
|
||||||
|
orderby: 'uniquekey DESC',
|
||||||
|
pageSize: 100
|
||||||
|
})
|
||||||
|
const curWorkList = response.rows
|
||||||
|
|
||||||
|
/**
|
||||||
|
* warn: 这里仅更新了finishpercent(进度条), 且当前作业布置推送新任务时, curWorkList中会查到新的任务与当前页面中this.classWorkList长度不一致,
|
||||||
|
* 故这里需循环this.classWorkList且只更新当前页面中的存在的任务进度
|
||||||
|
*/
|
||||||
|
for (let t = 0; t < classWorkList.value.length; t++) {
|
||||||
|
// 当前时间超过[作业任务]截止时间的跳过
|
||||||
|
// if( getDateTime > classWorkList.value[t].deaddate ){
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// 确保当前拿到的任务与页面中存在的任务能一对一(避免因删除其他操作而删除作业任务导致两个数组的index不统一而越界)
|
||||||
|
let curWork = curWorkList.find((work) => work.id === classWorkList.value[t].id)
|
||||||
|
// workdataresultcount 完成人数 workdatacount人数要大于0
|
||||||
|
if (curWork && curWork.workdataresultcount > 0 && classWorkList.value[t].workdatacount > 0) {
|
||||||
|
classWorkList.value[t].workdataresultcount = curWork.workdataresultcount
|
||||||
|
// 桌面端貌似不需要进度条了?
|
||||||
|
classWorkList.value[t].finishpercent = parseInt(
|
||||||
|
(classWorkList.value[t].workdataresultcount / classWorkList.value[t].workdatacount) * 100
|
||||||
|
)
|
||||||
|
// 计算参与学习任务的平均用时
|
||||||
|
if (classWorkList.value[t].workdatafeedbackcount > 0) {
|
||||||
|
classWorkList.value[t].averagetime = (classWorkList.value[t].feedtimelength / classWorkList.value[t].workdatafeedbackcount).toFixed(0)
|
||||||
|
} else {
|
||||||
|
classWorkList.value[t].averagetime = 0
|
||||||
|
}
|
||||||
|
// 更新批阅数
|
||||||
|
classWorkList.value[t].teacherrationgcount = curWork.teacherrationgcount
|
||||||
|
} else {
|
||||||
|
classWorkList.value[t].finishpercent = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取多个班级学生作业数据
|
||||||
|
const getStudentClassWorkData = () => {
|
||||||
|
// 再查找多个班级里,每个学生的作业数据
|
||||||
|
listClassworkdata({
|
||||||
|
classids: classListIds.value.join(','),
|
||||||
|
//entpcourseid: '', // 章节id? 这里要全课程的作业 不分章节? 根据学段学科查询所有的作业
|
||||||
|
edustage: userStore.edustage,// 学段
|
||||||
|
edusubject: userStore.edusubject,//学科
|
||||||
|
orderby: "deaddate DESC",
|
||||||
|
pageSize: 1000
|
||||||
|
}).then((res) => {
|
||||||
|
for (var t = 0; t < classWorkList.value.length; t++) {
|
||||||
|
for (var i = 0; i < res.rows.length; i++) {
|
||||||
|
//if (res.rows[i].uniquekey == classWorkList.value[t].uniquekey) {
|
||||||
|
if (res.rows[i].classworkid == classWorkList.value[t].id && res.rows[i].resultcount > 0) {
|
||||||
|
console.log('==================')
|
||||||
|
// 有几个学生完成/正在完成学习任务
|
||||||
|
// 至少resultcount不是0
|
||||||
|
classWorkList.value[t].workdatafeedbackcount++
|
||||||
|
|
||||||
|
// 在参与学习任务的人中,汇总计算用时
|
||||||
|
classWorkList.value[t].feedtimelength += parseInt(res.rows[i].finishtimelength)
|
||||||
|
|
||||||
|
// 计算得分率
|
||||||
|
if (
|
||||||
|
res.rows[i].classworkevallist != '' &&
|
||||||
|
res.rows[i].classworkevallist != null &&
|
||||||
|
res.rows[i].classworkevallist != 'null'
|
||||||
|
) {
|
||||||
|
let replacedString = res.rows[i].classworkevallist.replace(/""/g, '"')
|
||||||
|
// 将标签中双引号改为转义, 测试数据: "{\"id\":172907, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":358520, \"feedcontent\":\"④①⑤③②\", \"score\":4, \"rightanswer\":\"④①⑤③②\"},{\"id\":172908, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":358521, \"feedcontent\":\"气壮山威,鲲鹏展翅楚云飞\", \"score\":4, \"rightanswer\":\"志远天高,春风杨柳麓山青\"},{\"id\":172909, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":363096, \"feedcontent\":\"《红烛》化用“蜡矩”这一古典意象,赋予它新的含义,赞美了红烛以“蜡炬成灰”来点亮世界的奉献精神。\", \"score\":4, \"rightanswer\":\"《立在地球边上放号》中,全诗采用间接抒情的方式,描绘了太平洋的浪潮,吟唱了一曲惊心动魄的力的颂歌,意在赞美摧毁旧世界、创造新生活的“五四”精神。\"},{\"id\":172910, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":363098, \"feedcontent\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\", \"score\":4, \"rightanswer\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\"},{\"id\":172911, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":363100, \"feedcontent\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\", \"score\":4, \"rightanswer\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\"}"
|
||||||
|
replacedString = escapeHtmlQuotes(res.rows[i].classworkevallist).replace(
|
||||||
|
/"(\[.*\])"/g,
|
||||||
|
'$1'
|
||||||
|
)
|
||||||
|
replacedString = escapeHtmlQuotes(res.rows[i].classworkevallist)
|
||||||
|
var evalarray
|
||||||
|
try {
|
||||||
|
evalarray = JSON.parse('[' + res.rows[i].classworkevallist + ']')
|
||||||
|
} catch {
|
||||||
|
evalarray = JSON.parse('[' + replacedString + ']')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var e = 0; e < evalarray.length; e++) {
|
||||||
|
if (res.rows[i].worktype == '常规作业') {
|
||||||
|
evalarray[e].feedcontent = escapeHtmlQuotes(evalarray[e].feedcontent).replace(
|
||||||
|
/"(\[.*\])"/g,
|
||||||
|
'$1'
|
||||||
|
)
|
||||||
|
evalarray[e].feedcontent = escapeHtmlQuotes(evalarray[e].feedcontent)
|
||||||
|
}
|
||||||
|
if (evalarray[e].feedcontent == evalarray[e].rightanswer) {
|
||||||
|
// 正确,得分
|
||||||
|
classWorkList.value[t].rightAnswerCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 当前这个学习任务,共推送给了几个学生,workdatacount
|
||||||
|
if (res.rows[i].classworkid == classWorkList.value[t].id) {
|
||||||
|
classWorkList.value[t].workdatalist.push(res.rows[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 计算完成进度 workdatacount人数要大于0
|
||||||
|
if (
|
||||||
|
classWorkList.value[t].workdataresultcount > 0 &&
|
||||||
|
classWorkList.value[t].workdatacount > 0
|
||||||
|
) {
|
||||||
|
classWorkList.value[t].finishpercent = parseInt(
|
||||||
|
(classWorkList.value[t].workdataresultcount / classWorkList.value[t].workdatacount) * 100
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
classWorkList.value[t].finishpercent = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以下四个参数,都要计算
|
||||||
|
// 2024-04-12,酉阳,by jackyshen
|
||||||
|
|
||||||
|
// 计算参与学习任务的平均用时
|
||||||
|
if (classWorkList.value[t].workdatafeedbackcount > 0) {
|
||||||
|
classWorkList.value[t].averagetime = (classWorkList.value[t].feedtimelength / classWorkList.value[t].workdatafeedbackcount).toFixed(0)
|
||||||
|
} else {
|
||||||
|
classWorkList.value[t].averagetime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算批阅异常,需要获取每个题目的类型,找出主观题
|
||||||
|
// 暂缓
|
||||||
|
|
||||||
|
// 计算平均得分率: 正确题数/(题目总数*学生人数)*100
|
||||||
|
if (
|
||||||
|
classWorkList.value[t].entpcourseworklistarray &&
|
||||||
|
classWorkList.value[t].entpcourseworklistarray.length > 0
|
||||||
|
) {
|
||||||
|
var dd =
|
||||||
|
(classWorkList.value[t].rightAnswerCount /
|
||||||
|
(classWorkList.value[t].entpcourseworklistarray.length *
|
||||||
|
classWorkList.value[t].workdatacount)) *
|
||||||
|
100
|
||||||
|
classWorkList.value[t].scoingRate = dd.toFixed(0) + '%'
|
||||||
|
} else {
|
||||||
|
classWorkList.value[t].scoingRate = '0%'
|
||||||
|
}
|
||||||
|
// 设定典型作答,需要获取每个题目的类型,找出主观题
|
||||||
|
// 暂缓
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [dataList, toolStore.isToolWin],
|
||||||
|
() => {
|
||||||
|
console.log('====', toolStore)
|
||||||
|
setTimeout(() => {
|
||||||
|
getData() // 加载数据
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.class-reserv-wrap {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 15px 30px;
|
||||||
|
.class-reserv-tabs {
|
||||||
|
width: 30%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.class-reserv-body {
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<div class="common-layout" style="width: 100%; height: 73vh;">
|
||||||
|
<el-container>
|
||||||
|
<el-container>
|
||||||
|
<el-header style="height: auto">
|
||||||
|
<!--学情分布-->
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div style="font-size: 20px;font-weight: bold">
|
||||||
|
学情分布
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Distribution></Distribution>
|
||||||
|
</el-card>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<!-- 时长分析-->
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div style="font-size: 20px;font-weight: bold">
|
||||||
|
时长分析
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<TimeAnalyse></TimeAnalyse>
|
||||||
|
</el-card>
|
||||||
|
</el-main>
|
||||||
|
<el-footer style="height: auto;margin-bottom: 20px;">
|
||||||
|
<!--知识点概况-->
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div style="font-size: 20px;font-weight: bold">
|
||||||
|
知识点概览
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Konwledge></Konwledge>
|
||||||
|
</el-card>
|
||||||
|
</el-footer>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Distribution from '@/views/classTask/container/classOverview/distribution.vue'
|
||||||
|
import Konwledge from '@/views/classTask/container/classOverview/knowledge.vue'
|
||||||
|
import TimeAnalyse from '@/views/classTask/container/classOverview/timeAnalyse.vue'
|
||||||
|
import {defineProps,watch} from 'vue'
|
||||||
|
import overviewStore from "@/store/modules/overview";
|
||||||
|
// import {getBindlist} from "@/api/education/knowledgePoint";
|
||||||
|
const useOverview = overviewStore()
|
||||||
|
const props = defineProps({
|
||||||
|
tableList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// evalId:{
|
||||||
|
// type: Number,
|
||||||
|
// default: 0
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
watch(() => props.tableList,() => {
|
||||||
|
useOverview.getTableList(props.tableList)
|
||||||
|
},{deep:true})
|
||||||
|
</script>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div class="common-layout">
|
||||||
|
<el-container>
|
||||||
|
<el-aside width="400px">
|
||||||
|
<!-- 柱状图学情分布-->
|
||||||
|
<Echarts></Echarts>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<!-- 列表分布的人员-->
|
||||||
|
<StuList></StuList>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Echarts from '@/views/classTask/container/classOverview/distribution/echarts.vue'
|
||||||
|
import StuList from "@/views/classTask/container/classOverview/distribution/stuList.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,109 @@
|
||||||
|
<template>
|
||||||
|
<div className="chart-container">
|
||||||
|
<div ref="chartRef" className="chart"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref,nextTick,watch} from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import overviewStore from '@/store/modules/overview'
|
||||||
|
const useOverview = overviewStore()
|
||||||
|
|
||||||
|
// 获取图表容器的引用
|
||||||
|
const chartRef = ref(null);
|
||||||
|
|
||||||
|
// 数据源
|
||||||
|
const dataList = ref([
|
||||||
|
{name: '优', value: 0,rating:1},
|
||||||
|
{name: '优-', value: 0,rating:2},
|
||||||
|
{name: '良', value: 0,rating:3},
|
||||||
|
{name: '良-', value: 0,rating:4},
|
||||||
|
{name: '差', value: 0,rating:5},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 根据数据生成不同的颜色
|
||||||
|
function getColor(index) {
|
||||||
|
// 这里可以根据值生成不同的颜色
|
||||||
|
const colors = ['#d14a61','#675bba', '#e89110','#008c8c','#5793f3'];
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
function initChart() {
|
||||||
|
const myChart = echarts.init(chartRef.value);
|
||||||
|
const options = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: dataList.value.map(item => item.name),
|
||||||
|
axisTick: {
|
||||||
|
alignWithLabel: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
name: '数据',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: '30%',
|
||||||
|
data: dataList.value.map(item => item.value),
|
||||||
|
itemStyle: {
|
||||||
|
color: function (params) {
|
||||||
|
// 确保这里返回正确的颜色
|
||||||
|
return getColor(params.dataIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 显示柱子顶部的值
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
formatter: '{c}人',
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
myChart.setOption(options);
|
||||||
|
}
|
||||||
|
// 获取表的数据
|
||||||
|
const showEcharts =() => {
|
||||||
|
useOverview.tableList.forEach(item => {
|
||||||
|
const index = dataList.value.findIndex(item1 => item1.rating === item.rating)
|
||||||
|
if(index !== -1)
|
||||||
|
dataList.value[index].value ++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watch(() => useOverview.tableList,() => {
|
||||||
|
showEcharts()
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,115 @@
|
||||||
|
<template>
|
||||||
|
<el-tabs :tab-position="tabPosition" style="height: 100%" class="demo-tabs" @tabChange="handelChange">
|
||||||
|
<template v-for="(item,index) in leftList" :key="index">
|
||||||
|
<el-tab-pane :label="item.label" style="text-align:left">
|
||||||
|
<template v-if="item.stuList.length > 0">
|
||||||
|
<template v-for="(stuItem,stuIndex) in item.stuList" :key="stuIndex">
|
||||||
|
<el-tag style="margin:5px 10px 0 0" type="primary">{{stuItem.studentname}}</el-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-empty description="该分段暂时没有学生" />
|
||||||
|
</template>
|
||||||
|
</el-tab-pane>
|
||||||
|
</template>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {nextTick, ref, watch} from 'vue'
|
||||||
|
import overviewStore from '@/store/modules/overview'
|
||||||
|
|
||||||
|
const useOverview = overviewStore()
|
||||||
|
const tabPosition = ref('left')
|
||||||
|
const leftList = ref([
|
||||||
|
{
|
||||||
|
label:'优',
|
||||||
|
stuList:[],
|
||||||
|
rating:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label:'优-',
|
||||||
|
stuList:[],
|
||||||
|
rating:2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label:'良',
|
||||||
|
stuList:[],
|
||||||
|
rating:3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label:'良-',
|
||||||
|
stuList:[],
|
||||||
|
rating:4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label:'差',
|
||||||
|
stuList:[],
|
||||||
|
rating:5
|
||||||
|
},
|
||||||
|
])
|
||||||
|
//切换展示区域学生
|
||||||
|
const handelChange = (item) => {
|
||||||
|
showStudents(item)
|
||||||
|
}
|
||||||
|
//取区域的学生
|
||||||
|
const showStudents = (index) => {
|
||||||
|
console.log(useOverview.tableList,'lef')
|
||||||
|
leftList.value[index].stuList = useOverview.tableList.filter(item => {
|
||||||
|
if(item.rating == leftList.value[index].rating) return item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watch(() => useOverview.tableList,() => {
|
||||||
|
showStudents(0)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.el-tabs__item) {
|
||||||
|
position: relative; /* 使 ::before 相对于自身定位 */
|
||||||
|
padding-left: 24px; /* 增加左边距以留出圆圈的位置 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 圆圈样式 */
|
||||||
|
:deep(.el-tabs__item::before) {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #409eff; /* 创建空心圆圈 */
|
||||||
|
background-color: transparent; /* 设置背景颜色为透明 */
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
/* 根据索引设置不同的颜色 */
|
||||||
|
:deep(.el-tabs__item:nth-child(1)::before) {
|
||||||
|
border-color: #5793f3;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item:nth-child(2)::before) {
|
||||||
|
border-color: #d14a61;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item:nth-child(3)::before) {
|
||||||
|
border-color: #675bba;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item:nth-child(4)::before) {
|
||||||
|
border-color: #e89110;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item:nth-child(5)::before) {
|
||||||
|
border-color: #008c8c;
|
||||||
|
}
|
||||||
|
/* 选中状态下的样式 */
|
||||||
|
:deep(.el-tabs__item.is-active::before) {
|
||||||
|
background-color: transparent; /* 改变选中状态下的圆圈颜色 */
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item.is-active){
|
||||||
|
background-color: rgb(238, 241, 246);
|
||||||
|
}
|
||||||
|
:deep(.el-tabs--left .el-tabs__item.is-left){
|
||||||
|
text-align: left;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
style="width: 100%; margin-bottom: 20px;min-height: 300px;"
|
||||||
|
row-key="id"
|
||||||
|
border
|
||||||
|
default-expand-all
|
||||||
|
>
|
||||||
|
<el-table-column prop="title" label="知识点"/>
|
||||||
|
<el-table-column prop="allPoint" label="分值" sortable/>
|
||||||
|
<el-table-column prop="point" label="平均分" sortable/>
|
||||||
|
<el-table-column prop="scoingRate" label="得分率" sortable>
|
||||||
|
<template #default="scope">
|
||||||
|
<div>{{scope.row.scoingRate + '%'}}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, watch} from 'vue'
|
||||||
|
import overviewStore from '@/store/modules/overview'
|
||||||
|
import {listEntpcoursework} from '@/api/education/entpCourseWork'
|
||||||
|
|
||||||
|
const useOverview = overviewStore()
|
||||||
|
const tableData = ref([])
|
||||||
|
//获取题目id
|
||||||
|
const ids = ref('')
|
||||||
|
//总分
|
||||||
|
const allScore = ref(0)
|
||||||
|
//用来获取所有知识点
|
||||||
|
const konwledge = ref([])
|
||||||
|
//所有题目的知识点
|
||||||
|
const getKonwledge = () => {
|
||||||
|
useOverview.tableList.forEach(item => {
|
||||||
|
if(item.knowledgePoint){
|
||||||
|
konwledge.value.push({...JSON.parse(item.knowledgePoint),...{scoingRate:Number(item.scoingRate),point:item.point,allPoint:allScore.value}})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
tableData.value = getTableList(konwledge.value)
|
||||||
|
tableData.value = tableData.value.map(item => {
|
||||||
|
return{
|
||||||
|
...item,
|
||||||
|
allPoint: allScore.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(tableData.value,'tableData.value')
|
||||||
|
}
|
||||||
|
//获取总分
|
||||||
|
const getScore = async () => {
|
||||||
|
const scoreId = useOverview.tableList[0].entpcourseworklist
|
||||||
|
const fixedJsonString = `[${scoreId}]`;
|
||||||
|
const objects = JSON.parse(fixedJsonString);
|
||||||
|
const id = objects.map(obj => obj.id);
|
||||||
|
ids.value = id.join(',')
|
||||||
|
const res = await listEntpcoursework({ids: ids.value, pageSize: 500})
|
||||||
|
if(res.code === 200){
|
||||||
|
allScore.value = res.rows.reduce((acc, cur) => acc + cur.workScore, 0);
|
||||||
|
getKonwledge()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//组装tableList表格
|
||||||
|
const getTableList = (data) => {
|
||||||
|
const result = [];
|
||||||
|
data.forEach(item => {
|
||||||
|
const existingItem = result.find(i => i.id === item.id);
|
||||||
|
if (existingItem) {
|
||||||
|
// 累加point和scoingRate
|
||||||
|
existingItem.pointTotal += parseInt(item.point);
|
||||||
|
existingItem.scoingRateTotal += parseFloat(item.scoingRate);
|
||||||
|
existingItem.count++;
|
||||||
|
} else {
|
||||||
|
// 新的对象
|
||||||
|
result.push({
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
pointTotal: item.point,
|
||||||
|
scoingRateTotal: parseFloat(item.scoingRate),
|
||||||
|
count: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算平均值
|
||||||
|
result.forEach(item => {
|
||||||
|
item.point = Math.round(item.pointTotal / item.count);
|
||||||
|
// item.scoingRate = Math.round((item.scoingRateTotal / item.count) * 100) / 100;
|
||||||
|
item.scoingRate = Math.round((item.point / allScore.value) * 100);
|
||||||
|
delete item.pointTotal;
|
||||||
|
delete item.scoingRateTotal;
|
||||||
|
delete item.count;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
watch(() => useOverview.tableList,() => {
|
||||||
|
console.log(useOverview.tableList,'useOverview.tableList')
|
||||||
|
getScore()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,184 @@
|
||||||
|
<template>
|
||||||
|
<div class="chart-container">
|
||||||
|
<div ref="chartRef" class="chart"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import {ref, nextTick, watch} from 'vue'
|
||||||
|
import overviewStore from '@/store/modules/overview'
|
||||||
|
|
||||||
|
const useOverview = overviewStore()
|
||||||
|
|
||||||
|
// 获取图表容器的引用
|
||||||
|
const chartRef = ref(null);
|
||||||
|
|
||||||
|
const estimateTime = ref([]);
|
||||||
|
|
||||||
|
const avaterTime = ref([]);
|
||||||
|
// x轴
|
||||||
|
const xAxisData = ref([]);
|
||||||
|
|
||||||
|
// 获得 y 轴的数据
|
||||||
|
const getyAxisData = () => {
|
||||||
|
estimateTime.value = [];
|
||||||
|
avaterTime.value = [];
|
||||||
|
useOverview.tableList.forEach(item => {
|
||||||
|
if (item.rating !== 0) {
|
||||||
|
estimateTime.value.push({
|
||||||
|
name: item.scoingRate ? item.scoingRate + '%' : 0 + '%',
|
||||||
|
value: Number(item.timelength)
|
||||||
|
});
|
||||||
|
avaterTime.value.push({
|
||||||
|
name: item.scoingRate ? item.scoingRate + '%' : 0 + '%',
|
||||||
|
value: Number(item.finishtimelength)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// x 轴排序
|
||||||
|
xAxisData.value.sort((a, b) => {
|
||||||
|
const aPercentage = parseInt(a.replace('%', ''));
|
||||||
|
const bPercentage = parseInt(b.replace('%', ''));
|
||||||
|
return aPercentage - bPercentage;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成 x 轴数据
|
||||||
|
generateXAxisData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成 x 轴数据
|
||||||
|
function generateXAxisData() {
|
||||||
|
// 超过8条开始计算x轴
|
||||||
|
if(estimateTime.value.length > 8){
|
||||||
|
const minScoreRate = 0;
|
||||||
|
const maxScoreRate = 100;
|
||||||
|
const numPoints = 6; // x 轴上的点数
|
||||||
|
const step = (maxScoreRate - minScoreRate) / (numPoints - 1);
|
||||||
|
|
||||||
|
xAxisData.value = [];
|
||||||
|
for (let i = 0; i < numPoints; i++) {
|
||||||
|
const scoreRate = minScoreRate + i * step;
|
||||||
|
xAxisData.value.push(scoreRate + '%');
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
let uniqueXAxisData = new Set();
|
||||||
|
estimateTime.value.forEach(item => {
|
||||||
|
// 添加元素到 Set 中,自动处理重复问题
|
||||||
|
uniqueXAxisData.add(item.name);
|
||||||
|
});
|
||||||
|
// 将 Set 转换成数组
|
||||||
|
xAxisData.value = Array.from(uniqueXAxisData);
|
||||||
|
// 对数组进行排序
|
||||||
|
xAxisData.value.sort((a, b) => {
|
||||||
|
const aPercentage = parseInt(a.replace('%', ''));
|
||||||
|
const bPercentage = parseInt(b.replace('%', ''));
|
||||||
|
return aPercentage - bPercentage;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
function initChart() {
|
||||||
|
const myChart = echarts.init(chartRef.value);
|
||||||
|
const options = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['预估时长', '平均用时']
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
top: '10%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
name: '得分率',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: '#999',
|
||||||
|
fontSize: 12,
|
||||||
|
padding: [0, 0, 10, 0]
|
||||||
|
},
|
||||||
|
data: xAxisData.value
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: '作业时长',
|
||||||
|
nameTextStyle: {
|
||||||
|
color: '#999',
|
||||||
|
fontSize: 12,
|
||||||
|
padding: [0, 0, 10, 0]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: `预估时长`,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 10,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#5793f3'
|
||||||
|
},
|
||||||
|
data: estimateTime.value.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.value
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `平均用时`,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 10,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#d14a61'
|
||||||
|
},
|
||||||
|
data: avaterTime.value.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
myChart.setOption(options);
|
||||||
|
}
|
||||||
|
//获取平均时长
|
||||||
|
const getAvaterTime = () => {
|
||||||
|
return useOverview.tableList.reduce((acc, cur) => acc + cur.finishtimelength, 0) / useOverview.tableList.length;
|
||||||
|
}
|
||||||
|
const getEstimateTime = () => {
|
||||||
|
return useOverview.tableList.reduce((acc, cur) => acc + cur.timelength, 0) / useOverview.tableList.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
watch(() => useOverview.tableList,() => {
|
||||||
|
getyAxisData()
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,976 @@
|
||||||
|
<template>
|
||||||
|
<el-form ref="classWorkFormScoreRef" :model="classWorkFormScore">
|
||||||
|
<!-- <div class="teacher_content" :style="{ height: dialogProps.maxheight + 'px' }"> -->
|
||||||
|
<div class="teacher_content" :style="{ height: '75vh' }">
|
||||||
|
<div style="font-size: 18px; width: 100%; padding: 5px 10px" class="sticky">
|
||||||
|
{{ classWorkFormScore.name }} 答题详情
|
||||||
|
</div>
|
||||||
|
<div class="teacher_content_con">
|
||||||
|
<!-- 题目内容:习题训练 -->
|
||||||
|
<div v-if="dialogProps.studentObj.worktype == '习题训练'">
|
||||||
|
<div v-for="(stuItem, sIndex) in dialogProps.studentQuizAllList" :key="stuItem.id">
|
||||||
|
<div v-for="quItem in dialogProps.quizlist" :key="quItem.id">
|
||||||
|
<div v-if="stuItem.entpcourseworkid == quItem.id">
|
||||||
|
<el-card style="max-width: 100%; margin-bottom: 10px">
|
||||||
|
<!-- 题型 分值 -->
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span
|
||||||
|
>{{ sIndex + 1 }}、{{ quItem.worktype }}
|
||||||
|
{{ stuItem.score ? stuItem.score : 0 }}分</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 习题训练 -->
|
||||||
|
<div v-if="dialogProps.studentObj.worktype == '习题训练'">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24" style="padding: 10px">
|
||||||
|
<!-- 题源、题目标题、题目选项 -->
|
||||||
|
<span>{{ quItem.worktag }}</span>
|
||||||
|
<span style="margin-left: 4px" v-html="quItem.titleFormat"></span>
|
||||||
|
<div :span="24" style="padding: 10px" v-html="quItem.workdescFormat"></div>
|
||||||
|
<!-- 折叠: 详情分析解答 -->
|
||||||
|
<div class="demo-collapse">
|
||||||
|
<el-collapse>
|
||||||
|
<el-collapse-item title="详情分析解答" name="1">
|
||||||
|
<el-row style="padding: 1% 4%; border: 2px dotted">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-col :span="2" style="padding: 10px 0px"
|
||||||
|
><em>【答案】</em></el-col
|
||||||
|
>
|
||||||
|
<el-col
|
||||||
|
:span="21"
|
||||||
|
style="padding: 10px 0px"
|
||||||
|
v-html="quItem.workanswerFormat"
|
||||||
|
></el-col>
|
||||||
|
<el-col :span="2" style="padding: 10px 0px"
|
||||||
|
><em>【分析】</em></el-col
|
||||||
|
>
|
||||||
|
<el-col
|
||||||
|
:span="21"
|
||||||
|
style="padding: 10px 0px"
|
||||||
|
v-html="quItem.method"
|
||||||
|
></el-col>
|
||||||
|
<el-col :span="2" style="padding: 10px 0px"
|
||||||
|
><em>【解答】</em></el-col
|
||||||
|
>
|
||||||
|
<el-col
|
||||||
|
:span="21"
|
||||||
|
style="padding: 10px 0px"
|
||||||
|
v-html="quItem.analyse"
|
||||||
|
></el-col>
|
||||||
|
<el-col :span="2" style="padding: 10px 0px"
|
||||||
|
><em>【点评】</em></el-col
|
||||||
|
>
|
||||||
|
<el-col
|
||||||
|
:span="21"
|
||||||
|
style="padding: 10px 0px"
|
||||||
|
v-html="quItem.discuss"
|
||||||
|
></el-col>
|
||||||
|
</template>
|
||||||
|
</el-row>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
<!-- 答案 -->
|
||||||
|
<template #footer>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="6" style="padding: 10px">
|
||||||
|
<span
|
||||||
|
>参考答案:
|
||||||
|
<span v-if="quItem.workanswerFormat != ''">
|
||||||
|
<sapn
|
||||||
|
style="
|
||||||
|
background-color: #0ed116;
|
||||||
|
color: white;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
"
|
||||||
|
>{{ quItem.workanswerFormat.replace(/<[^>]+>/g, '') }}</sapn
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" style="padding: 10px">
|
||||||
|
<!-- <span>学生答案:{{ stuItem.feedcontent }}</span> -->
|
||||||
|
<span
|
||||||
|
>学生答案:
|
||||||
|
<span v-if="quItem.workdesc == ''|| quItem.workdesc == '[]' ">
|
||||||
|
<!-- quItem.workdesc 没值,说明是非选择题 -->
|
||||||
|
<span
|
||||||
|
v-if="stuItem.feedcontent != ''"
|
||||||
|
style="
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ stuItem.feedcontent.replace('#', '、') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<!-- 选择题类型:学生答题转换为 ABCD格式 -->
|
||||||
|
<span
|
||||||
|
v-if="stuItem.feedcontent != ''"
|
||||||
|
style="
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
JSON.parse(quItem.workdesc)
|
||||||
|
.map((item, index) => {
|
||||||
|
if (item == stuItem.feedcontent) {
|
||||||
|
return String.fromCharCode(65 + Number(index))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean)[0]
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" style="padding: 10px">
|
||||||
|
<div
|
||||||
|
v-for="(imageItem, index) in stuItem.imagefile"
|
||||||
|
v-if="stuItem.imagefile && stuItem.imagefile.length > 0"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<el-image
|
||||||
|
style="width: 30px; height: 30px"
|
||||||
|
:src="imageItem"
|
||||||
|
:zoom-rate="1.2"
|
||||||
|
:max-scale="7"
|
||||||
|
:min-scale="0.2"
|
||||||
|
:preview-src-list="stuItem.imagefile"
|
||||||
|
:initial-index="4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" style="padding: 10px">
|
||||||
|
<!-- 单选题 填空题 多选题 判断题 主观题 复合题; 待完善:
|
||||||
|
1、目前只支持单选题、多选题不用老师批改分数,这里先禁止分值修改; -->
|
||||||
|
<el-input-number
|
||||||
|
v-model="classWorkFormScore.teacherRating[sIndex].score"
|
||||||
|
:min="0"
|
||||||
|
:max="classWorkFormScore.teacherRating[sIndex].maxScore"
|
||||||
|
size="small"
|
||||||
|
:disabled="
|
||||||
|
(quItem.worktype == '单选题' || quItem.worktype == '多选题') &&
|
||||||
|
stuItem.feedcontent == stuItem.rightanswer &&
|
||||||
|
stuItem.feedcontent != ''
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
"
|
||||||
|
></el-input-number>
|
||||||
|
分
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 题目内容:常规作业、课堂展示 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
dialogProps.studentObj.worktype == '常规作业' ||
|
||||||
|
dialogProps.studentObj.worktype == '课堂展示' ||
|
||||||
|
dialogProps.studentObj.worktype == '框架梳理'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div v-for="(stuItem, sIndex) in dialogProps.studentQuizAllList" :key="stuItem.id">
|
||||||
|
<el-card style="max-width: 100%; margin-bottom: 10px">
|
||||||
|
<!-- 题型 分值 -->
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ sIndex + 1 }}、{{ stuItem.score ? stuItem.score : 0 }}分</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 常规作业 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
dialogProps.studentObj.worktype == '常规作业' ||
|
||||||
|
dialogProps.studentObj.worktype == '课堂展示' ||
|
||||||
|
dialogProps.studentObj.worktype == '框架梳理'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- 文件内容格式:mp3/mp4/doc/docx/excel/pdf/ppt/pptx/jpg/jpeg/gif/png/txt ->
|
||||||
|
<-- 老师附件展示 -->
|
||||||
|
<!-- 折叠: 详情分析解答 -->
|
||||||
|
<div v-if="teacherFeedContentList.length > 0">
|
||||||
|
<div class="demo-collapse" style="border: 2px dotted">
|
||||||
|
<el-collapse>
|
||||||
|
<el-collapse-item title="老师布置详情" name="1">
|
||||||
|
<div class="image_list">
|
||||||
|
<div v-if="teachImageList.length > 0">
|
||||||
|
<div style="margin-bottom: 5px">
|
||||||
|
<span style="color: red">温馨提示:点击图片可放大预览 </span>
|
||||||
|
</div>
|
||||||
|
<div v-for="(imageItem, index) in teachImageList" :key="index">
|
||||||
|
<el-image
|
||||||
|
style="width: 400px; height: 400px"
|
||||||
|
:src="imageItem.url"
|
||||||
|
:zoom-rate="1.2"
|
||||||
|
:max-scale="7"
|
||||||
|
:min-scale="0.2"
|
||||||
|
:preview-src-list="
|
||||||
|
teachImageList
|
||||||
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
item.name.indexOf('jpg') > -1 ||
|
||||||
|
item.name.indexOf('jpeg') > -1 ||
|
||||||
|
item.name.indexOf('png') > -1
|
||||||
|
)
|
||||||
|
.map((item) => item.url)
|
||||||
|
"
|
||||||
|
:initial-index="4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="teachFileList.length > 0">
|
||||||
|
<div style="margin: 10px 0">
|
||||||
|
<span style="color: red" @click="openFile"
|
||||||
|
>温馨提示:点击此处 可预览其他类型附件!
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学生答题展示 -->
|
||||||
|
<div v-if="feedContentList.length > 0">
|
||||||
|
<p>学生答题附件内容</p>
|
||||||
|
<div class="image_list">
|
||||||
|
<div v-if="imageList.length > 0">
|
||||||
|
<div style="margin-bottom: 5px">
|
||||||
|
<span style="color: red">温馨提示:点击图片可放大预览 </span>
|
||||||
|
</div>
|
||||||
|
<div v-for="(imageItem, index) in imageList" :key="index">
|
||||||
|
<el-image
|
||||||
|
style="width: 500px; height: 500px"
|
||||||
|
:src="imageItem.url"
|
||||||
|
:zoom-rate="1.2"
|
||||||
|
:max-scale="7"
|
||||||
|
:min-scale="0.2"
|
||||||
|
:preview-src-list="
|
||||||
|
imageList
|
||||||
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
item.name.indexOf('jpg') > -1 ||
|
||||||
|
item.name.indexOf('jpeg') > -1 ||
|
||||||
|
item.name.indexOf('png') > -1
|
||||||
|
)
|
||||||
|
.map((item) => item.url)
|
||||||
|
"
|
||||||
|
:initial-index="4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="fileList.length > 0">
|
||||||
|
<div style="margin: 10px 0">
|
||||||
|
<span style="color: red" @click="openFile"
|
||||||
|
>温馨提示:点击此处 可预览其他类型附件!
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-empty
|
||||||
|
description="该学生还未作答"
|
||||||
|
style="width: 100%; height: 200px"
|
||||||
|
></el-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 答案 -->
|
||||||
|
<!-- <template #footer>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12" style="padding: 10px">
|
||||||
|
<el-input-number v-model="classWorkFormScore.teacherRating[sIndex].score"
|
||||||
|
:controls="false"
|
||||||
|
:min="0"
|
||||||
|
:max="classWorkFormScore.teacherRating[sIndex].maxScore"
|
||||||
|
size="small"
|
||||||
|
></el-input-number> 分
|
||||||
|
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template> -->
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 批改评价与评语 -->
|
||||||
|
<div class="tacher_conten_foot">
|
||||||
|
<el-row style="padding: 1% 4%; border: 2px dotted">
|
||||||
|
<el-col :span="24" style="display: flex; flex-direction: column">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="14">
|
||||||
|
<div style="display: flex; margin: 10px auto">
|
||||||
|
<span style="display: flex; align-items: center">
|
||||||
|
<span v-if="dialogProps.studentObj.worktype == '习题训练'">
|
||||||
|
得分:<span style="margin: 0; color: red">{{
|
||||||
|
classWorkFormScore.teacherRating.reduce((a, b) => a + b.score, 0).toFixed(2)
|
||||||
|
}}</span
|
||||||
|
>分
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
得分:
|
||||||
|
<span v-if="classWorkFormScore.teacherRating.length > 0">
|
||||||
|
<el-input-number
|
||||||
|
v-model="classWorkFormScore.teacherRating[0].score"
|
||||||
|
:controls="false"
|
||||||
|
type="number"
|
||||||
|
:min="0"
|
||||||
|
:max="classWorkFormScore.teacherRating[0].maxScore"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px"
|
||||||
|
@change="handleChange"
|
||||||
|
></el-input-number>
|
||||||
|
</span>
|
||||||
|
分
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<div class="score-container">
|
||||||
|
<div
|
||||||
|
v-for="(score, index) in teacherRatingList"
|
||||||
|
:key="index"
|
||||||
|
:class="[
|
||||||
|
'score-circle',
|
||||||
|
{ active: classWorkFormScore.rating == score.ratingKey }
|
||||||
|
]"
|
||||||
|
@click="selectScore(score)"
|
||||||
|
>
|
||||||
|
{{ score.ratingValue }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="10" style="display: flex; align-items: center">
|
||||||
|
<el-select
|
||||||
|
v-model="value"
|
||||||
|
placeholder="常用评语"
|
||||||
|
style="width: 240px"
|
||||||
|
@change="onSelectOption"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in cities"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button v-if="!isAdding" text bg size="small" @click="onAddOption">
|
||||||
|
新增常用语
|
||||||
|
</el-button>
|
||||||
|
<template v-else>
|
||||||
|
<el-input
|
||||||
|
v-model="optionName"
|
||||||
|
class="option-input"
|
||||||
|
placeholder="输入新的常用语"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" size="small" @click="onConfirm"> 确定 </el-button>
|
||||||
|
<el-button size="small" @click="clear">取消</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="display: flex; flex-direction: column">
|
||||||
|
<el-form-item label="评语说明">
|
||||||
|
<el-col :span="15" style="padding: 0px">
|
||||||
|
<el-input
|
||||||
|
v-model="classWorkFormScore.teacherremark"
|
||||||
|
type="textarea"
|
||||||
|
rows="3"
|
||||||
|
placeholder="请输入评语说明"
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<div style="margin: 10px">
|
||||||
|
<el-button type="primary" @click="onClassWorkFormScoreSave">批阅确认</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- :style="{ height: dialogProps.maxheight + 'px' }" -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="fileReadopen"
|
||||||
|
title="文件预览"
|
||||||
|
width="80%"
|
||||||
|
:style="{ height: '75vh' }"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<div class="file-read-dialog">
|
||||||
|
<div>
|
||||||
|
<!-- 老师附件 -->
|
||||||
|
<div v-if="teachFileList.length > 0">
|
||||||
|
<el-card style="max-width: 480px">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>老师文件列表</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-for="item in teachFileList"
|
||||||
|
:key="item"
|
||||||
|
style="margin: 10px; display: flex; align-items: center"
|
||||||
|
>
|
||||||
|
<span style="margin-right: 10px">{{ item.name }}</span>
|
||||||
|
<el-button type="primary" @click="onFileRead(item)">预览</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
<!-- 学生附件 -->
|
||||||
|
<div v-if="fileList.length > 0">
|
||||||
|
<el-card style="max-width: 480px">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>学生文件列表</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-for="item in fileList"
|
||||||
|
:key="item"
|
||||||
|
style="margin: 10px; display: flex; align-items: center"
|
||||||
|
>
|
||||||
|
<span style="margin-right: 10px">{{ item.name }}</span>
|
||||||
|
<el-button type="primary" @click="onFileRead(item)">预览</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 100%" :style="{ height: dialogProps.maxheight + 'px' }">
|
||||||
|
<div style="margin-left: 10px">
|
||||||
|
预览展示区域<span style="color: red; margin-left: 10px">
|
||||||
|
温馨提示:若预览失败,<span style="margin-left: 10px">{{ props.name }}</span
|
||||||
|
>可点击此处<a
|
||||||
|
:href="fileitem.url ? fileitem.url : ''"
|
||||||
|
target="_blank"
|
||||||
|
style="color: blue"
|
||||||
|
>下载!</a
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<ReFilePreview
|
||||||
|
:name="fileitem.name"
|
||||||
|
:type="fileitem.type"
|
||||||
|
:file-type="fileitem.type"
|
||||||
|
:file-path="fileitem.url"
|
||||||
|
:text-content="textContent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="classWorkAnalysisScoreDialogRef">
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
// import useAppStore from '@/store/modules/app'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
// import { Plus } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||||
|
import { updateClassworkeval, updateClassworkdata } from '@/api/classTask'
|
||||||
|
import { getTimeDate } from '@/utils/date'
|
||||||
|
import ReFilePreview from '@/components/refile-preview/index.vue'
|
||||||
|
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const emits = defineEmits(['class_work_score_submit'])
|
||||||
|
const props = defineProps({})
|
||||||
|
|
||||||
|
const fileReadopen = ref(false)
|
||||||
|
const analysisScoreOpen = ref(false)
|
||||||
|
|
||||||
|
const dialogProps = ref({
|
||||||
|
maxheight: 300,
|
||||||
|
studentObj: {}, //
|
||||||
|
studentQuizAllList: [], //学生答题的list
|
||||||
|
quizlist: [] // 当前学生的所有题目list
|
||||||
|
})
|
||||||
|
//学生常规作业list
|
||||||
|
const feedContentList = ref([])
|
||||||
|
const imageList = ref([])
|
||||||
|
const fileList = ref([])
|
||||||
|
// 老师常规作业、课堂展示布置题目附件list
|
||||||
|
const teacherFeedContentList = ref([])
|
||||||
|
const teachImageList = ref([])
|
||||||
|
const teachFileList = ref([])
|
||||||
|
|
||||||
|
// 提交分值批改
|
||||||
|
const classWorkFormScore = reactive({
|
||||||
|
name: '', // 学生姓名
|
||||||
|
score: 0, // 分值
|
||||||
|
teacherRating: [], // 老师批改题目list信息
|
||||||
|
rating: 0, // 评价星数 1-优 2-优减 3-良 4-良减 5-差
|
||||||
|
teacherremark: '' //评分说明
|
||||||
|
})
|
||||||
|
const teacherRatingList = ref([
|
||||||
|
{ ratingKey: '1', ratingValue: '优' },
|
||||||
|
{ ratingKey: '2', ratingValue: '优-' },
|
||||||
|
{ ratingKey: '3', ratingValue: '良' },
|
||||||
|
{ ratingKey: '4', ratingValue: '良-' },
|
||||||
|
{ ratingKey: '5', ratingValue: '差' }
|
||||||
|
])
|
||||||
|
// 确定的线上图片数据
|
||||||
|
//#region 文件内容相关
|
||||||
|
const fileitem = reactive({
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
url: ''
|
||||||
|
})
|
||||||
|
// 文件预览
|
||||||
|
const onFileRead = (file) => {
|
||||||
|
textContent.value = '1'
|
||||||
|
//
|
||||||
|
fileitem.type = file.name.split('.').pop().toLowerCase()
|
||||||
|
fileitem.url = file.url
|
||||||
|
fileitem.name = file.name
|
||||||
|
|
||||||
|
// txt 读取
|
||||||
|
if (fileitem.type == 'txt') {
|
||||||
|
loadFileTextContent(fileitem.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const openFile = () => {
|
||||||
|
console.log('打开文件弹窗!')
|
||||||
|
fileReadopen.value = true
|
||||||
|
}
|
||||||
|
// txt文件读取
|
||||||
|
const textContent = ref('')
|
||||||
|
const loadFileTextContent = async (url) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url)
|
||||||
|
if (!response.ok) {
|
||||||
|
textContent.value = '文件读取失败,您可以点击上方链接跳到另外页面查看'
|
||||||
|
throw new Error('文件读取失败')
|
||||||
|
}
|
||||||
|
textContent.value = await response.text()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取文件时出错:', error)
|
||||||
|
textContent.value = '文件读取失败,您可以点击上方链接跳到另外页面查看'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
// 输入数字,有e这个异类可输入,处理一下
|
||||||
|
// 输入e value为null
|
||||||
|
const handleChange = (value) => {
|
||||||
|
console.log(value, '=====')
|
||||||
|
console.log(typeof value, '===value==')
|
||||||
|
// if ((value+"").includes('e')) {
|
||||||
|
// classWorkFormScore.teacherRating[0].score = value.replace('e', '');
|
||||||
|
// }
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
console.log('??????')
|
||||||
|
classWorkFormScore.teacherRating[0].score = 0
|
||||||
|
} else {
|
||||||
|
classWorkFormScore.teacherRating[0].score = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region 常用评语相关
|
||||||
|
const isAdding = ref(false)
|
||||||
|
const value = ref([])
|
||||||
|
const optionName = ref('')
|
||||||
|
const cities = ref([
|
||||||
|
{
|
||||||
|
value: '需要继续加油努力哟!棒棒哒!',
|
||||||
|
label: '需要继续加油努力哟!棒棒哒!'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '做得很好',
|
||||||
|
label: '做得很好'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要改进',
|
||||||
|
label: '需要改进'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要更多练习',
|
||||||
|
label: '需要更多练习'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要更仔细',
|
||||||
|
label: '需要更仔细'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要更清晰',
|
||||||
|
label: '需要更清晰'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要更准确',
|
||||||
|
label: '需要更准确'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要更详细',
|
||||||
|
label: '需要更详细'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要更简洁',
|
||||||
|
label: '需要更简洁'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要更有条理',
|
||||||
|
label: '需要更有条理'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '需要更有创意',
|
||||||
|
label: '需要更有创意'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const onSelectOption = (option) => {
|
||||||
|
classWorkFormScore.teacherremark = option
|
||||||
|
}
|
||||||
|
const onAddOption = () => {
|
||||||
|
isAdding.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
if (optionName.value) {
|
||||||
|
cities.value.push({
|
||||||
|
label: optionName.value,
|
||||||
|
value: optionName.value
|
||||||
|
})
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
optionName.value = ''
|
||||||
|
isAdding.value = false
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
const selectScore = (score) => {
|
||||||
|
console.log(score, 'score----')
|
||||||
|
classWorkFormScore.rating = score.ratingKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收父组件传过来的参数
|
||||||
|
const acceptParams = (params) => {
|
||||||
|
console.log(params)
|
||||||
|
console.log(dialogProps, 'dialogProps')
|
||||||
|
// 先重置一下 参数
|
||||||
|
// 重置提交表单
|
||||||
|
classWorkFormScore.name = '' // 学生姓名
|
||||||
|
classWorkFormScore.score = 0 // 分值
|
||||||
|
classWorkFormScore.teacherRating = [] // 教师评分
|
||||||
|
classWorkFormScore.rating = 0 // 评价星数 1-优 2-优减 3-良 4-良减 5-差
|
||||||
|
classWorkFormScore.teacherremark = '' //评分说明
|
||||||
|
value.value = '' // 常用语重置
|
||||||
|
// 学生作业预览缓存清除
|
||||||
|
feedContentList.value = []
|
||||||
|
imageList.value = []
|
||||||
|
fileList.value = []
|
||||||
|
// 老师布置附件缓存清除
|
||||||
|
teacherFeedContentList.value = []
|
||||||
|
teachImageList.value = []
|
||||||
|
teachFileList.value = []
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
dialogProps.value = params
|
||||||
|
classWorkFormScore.name = params.studentObj.studentname
|
||||||
|
|
||||||
|
// 学生答题分值初始化
|
||||||
|
if (params.studentQuizAllList.length > 0) {
|
||||||
|
if (params.studentObj.worktype == '习题训练') {
|
||||||
|
params.studentQuizAllList.forEach((item) => {
|
||||||
|
params.quizlist.forEach((item2) => {
|
||||||
|
//
|
||||||
|
if (item.entpcourseworkid == item2.id) {
|
||||||
|
// 单选题 填空题 多选题 判断题 主观题 复合题; 待完善:
|
||||||
|
classWorkFormScore.teacherRating.push({
|
||||||
|
maxScore: item.score ? item.score : 100, //分值最大值 不能大于题目的分值 ?没有,那就最大100
|
||||||
|
score:
|
||||||
|
(item2.worktype == '单选题' || item2.worktype == '多选题') &&
|
||||||
|
item.feedcontent == item.rightanswer
|
||||||
|
? item.score
|
||||||
|
: item.teacherRating
|
||||||
|
? item.teacherRating
|
||||||
|
: 0, // 教师批改分值: 单选多选 默认批改为题目分值
|
||||||
|
id: item.id // 学生答题列表的id
|
||||||
|
})
|
||||||
|
classWorkFormScore.teacherremark = item.teacherremark // 评价语
|
||||||
|
|
||||||
|
// 主观题中学生回答有图片?imagefile: “” ; “[null]”; "[\"https://wzyzoss.ee2f687.png\"]"
|
||||||
|
if (item.imagefile != '') {
|
||||||
|
// 其他: !主观题 为“”;
|
||||||
|
const filelist = JSON.parse(item.imagefile)
|
||||||
|
console.log(filelist, '学生习题附带图片fimagefile-------------')
|
||||||
|
|
||||||
|
if (filelist && filelist.length > 0 && filelist[0] != null) {
|
||||||
|
item.imagefile = filelist
|
||||||
|
} else {
|
||||||
|
item.imagefile = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 学生回答
|
||||||
|
if (params.studentObj.worktype == '常规作业') {
|
||||||
|
try {
|
||||||
|
// 老师布置的附件 datacontent TODO:常规作业、其他类型还未接入
|
||||||
|
if (params.studentObj.datacontent != '') {
|
||||||
|
const teachWorkFileList = JSON.parse(params.studentObj.datacontent)
|
||||||
|
console.log(teachWorkFileList, '老师filelist-------------')
|
||||||
|
teachWorkFileList &&
|
||||||
|
teachWorkFileList.forEach((item) => {
|
||||||
|
if (
|
||||||
|
item.name.indexOf('jpg') > -1 ||
|
||||||
|
item.name.indexOf('jpeg') > -1 ||
|
||||||
|
item.name.indexOf('png') > -1
|
||||||
|
) {
|
||||||
|
teachImageList.value.push(item)
|
||||||
|
} else {
|
||||||
|
teachFileList.value.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
teacherFeedContentList.value.push(teachWorkFileList)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogProps.value.studentObj.datacontent = dialogProps.value.studentObj.datacontent
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Invalid JSON:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
params.studentQuizAllList.forEach((item) => {
|
||||||
|
classWorkFormScore.teacherRating.push({
|
||||||
|
maxScore: item.score ? item.score : 100, //分值最大值 不能大于题目的分值 ?没有,那就最大100
|
||||||
|
score: item.teacherRating, // 教师批改分值: 默认批改为题目分值
|
||||||
|
id: item.id // 学生答题列表的id
|
||||||
|
})
|
||||||
|
classWorkFormScore.teacherremark = item.teacherremark // 评价语
|
||||||
|
|
||||||
|
if (item.feedcontent != '') {
|
||||||
|
const filelist = JSON.parse(item.feedcontent)
|
||||||
|
console.log(filelist, '学生filelist-------------')
|
||||||
|
// 学生答题的内容 ? feedcontent应该只包含了一个数组?
|
||||||
|
filelist &&
|
||||||
|
filelist.forEach((item) => {
|
||||||
|
if (
|
||||||
|
item.name.indexOf('jpg') > -1 ||
|
||||||
|
item.name.indexOf('jpeg') > -1 ||
|
||||||
|
item.name.indexOf('png') > -1
|
||||||
|
) {
|
||||||
|
imageList.value.push(item)
|
||||||
|
} else {
|
||||||
|
fileList.value.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
feedContentList.value.push(filelist)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (
|
||||||
|
params.studentObj.worktype == '课堂展示' ||
|
||||||
|
params.studentObj.worktype == '框架梳理'
|
||||||
|
) {
|
||||||
|
params.studentQuizAllList.forEach((item) => {
|
||||||
|
classWorkFormScore.teacherRating.push({
|
||||||
|
maxScore: item.score ? item.score : 100, //分值最大值 不能大于题目的分值 ?没有,那就最大100
|
||||||
|
score: item.teacherRating, // 教师批改分值: 默认批改为题目分值
|
||||||
|
id: item.id // 学生答题列表的id
|
||||||
|
})
|
||||||
|
classWorkFormScore.teacherremark = item.teacherremark // 评价语
|
||||||
|
// 根据移动端返回定义:只返回一张图片:rightanswer:"https://wzyzoss.eos-chongqing-3.cmecloud.cn/2024/9/5/c5d8e00a93364dd3b975f669afa217f9.png"
|
||||||
|
// 这里只有一张图片;
|
||||||
|
console.log(item.rightanswer, '----------课堂展示学生答题------------------')
|
||||||
|
|
||||||
|
if (item.rightanswer != '' && item.rightanswer != null) {
|
||||||
|
if (
|
||||||
|
item.rightanswer.indexOf('jpg') > -1 ||
|
||||||
|
item.rightanswer.indexOf('jpeg') > -1 ||
|
||||||
|
item.rightanswer.indexOf('png') > -1
|
||||||
|
) {
|
||||||
|
const imgeobj = {
|
||||||
|
name: item.rightanswer,
|
||||||
|
url: item.rightanswer
|
||||||
|
}
|
||||||
|
// 放入图片list
|
||||||
|
imageList.value.push(imgeobj)
|
||||||
|
// 没其他附件类型
|
||||||
|
//fileList.value.push(?);
|
||||||
|
feedContentList.value.push(imgeobj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 其他类型待联调
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 老师批过优良,则默认批改的优良,不然就是默认的0
|
||||||
|
console.log(params.studentQuizAllList[0].rating, '----------------------------')
|
||||||
|
// 为null 或0 则默认为0
|
||||||
|
classWorkFormScore.rating =
|
||||||
|
params.studentQuizAllList[0].rating == 0 ? 0 : params.studentQuizAllList[0].rating
|
||||||
|
}
|
||||||
|
|
||||||
|
analysisScoreOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交批改评分
|
||||||
|
const onClassWorkFormScoreSave = () => {
|
||||||
|
console.log(classWorkFormScore)
|
||||||
|
// rating评价 不为0 则批改过
|
||||||
|
if (dialogProps.value.studentQuizAllList && dialogProps.value.studentQuizAllList[0].rating != 0) {
|
||||||
|
ElMessageBox.confirm(`该学生已批改,再次批改会覆盖之前的评分!`, '温馨提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
draggable: true
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
onSubmit()
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
} else {
|
||||||
|
onSubmit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onSubmit = () => {
|
||||||
|
if (classWorkFormScore.rating == 0) {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: '请您给学生一个评价吧!'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var formd = {
|
||||||
|
id: dialogProps.value.studentObj.id, // this.activeClassWork.id;
|
||||||
|
status: '1',//0 未批阅; 1 已批阅
|
||||||
|
updatedate: getTimeDate(),// = year+'-'+month+'-'+day+' '+hh+':'+mm;
|
||||||
|
};
|
||||||
|
// 更新作业批改状态
|
||||||
|
updateClassworkdata(formd).then(res => {
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新题目批改
|
||||||
|
classWorkFormScore.teacherRating &&
|
||||||
|
classWorkFormScore.teacherRating.map((item, index) => {
|
||||||
|
const queryParams = {
|
||||||
|
id: item.id,
|
||||||
|
teacherRating: item.score, // 教师评分
|
||||||
|
rating: classWorkFormScore.rating, // 评价
|
||||||
|
teacherremark: classWorkFormScore.teacherremark, //评分说明
|
||||||
|
timestamp: getTimeDate() // 时间
|
||||||
|
}
|
||||||
|
console.log(queryParams)
|
||||||
|
updateClassworkeval(queryParams).then((res) => {
|
||||||
|
// if(res.code == 200){
|
||||||
|
// 循环提交能干个啥
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: '提交成功!'
|
||||||
|
})
|
||||||
|
analysisScoreOpen.value = false
|
||||||
|
// 清空答题清空右侧 题目列表
|
||||||
|
emits('class_work_score_submit')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化请求
|
||||||
|
// onMounted(() => {})
|
||||||
|
|
||||||
|
// 暴露给父组件的参数和方法(外部需要什么,都可以从这里暴露出去)
|
||||||
|
defineExpose({
|
||||||
|
acceptParams
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.teacher_content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.teacher_content_con {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.tacher_conten_foot {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image_list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
flex-direction: column;
|
||||||
|
/* justify-content: space-evenly; */
|
||||||
|
}
|
||||||
|
/* .el-dialog__body{
|
||||||
|
padding: 0!important;
|
||||||
|
height: 100%;
|
||||||
|
} */
|
||||||
|
.file-read-dialog {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
/* margin-bottom: 20px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-circle {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: pink;
|
||||||
|
color: red;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-circle.active {
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,754 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="classWorkAnalysis.open"
|
||||||
|
:modal-append-to-body="false"
|
||||||
|
class="clwk_dialog"
|
||||||
|
style="width: 90%; height: 85vh"
|
||||||
|
:show-close="false"
|
||||||
|
top="8vh"
|
||||||
|
append-to-body
|
||||||
|
destory-on-close
|
||||||
|
:before-close="onBeforeClose"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div style="font-size: 18px; display: flex; flex-wrap: nowrap">
|
||||||
|
<div style="flex: 1">
|
||||||
|
{{ classWorkAnalysis.title }}答题情况
|
||||||
|
<el-tag :type="classWorkAnalysis.workclass" size="large" style="height: 25px">{{
|
||||||
|
classWorkAnalysis.worktype
|
||||||
|
}}</el-tag>
|
||||||
|
</div>
|
||||||
|
<!-- classWorkAnalysis.entpcourseworklistarray 当前学习任务所包含的试题ID -->
|
||||||
|
<el-row
|
||||||
|
v-if="classWorkAnalysis.entpcourseworklistarray.length > 0"
|
||||||
|
style="margin: 0 auto; flex: 1"
|
||||||
|
>
|
||||||
|
<el-button-group style="margin-bottom: 10px">
|
||||||
|
<el-button
|
||||||
|
:type="classWorkAnalysis.view == 'studentview' ? 'success' : ''"
|
||||||
|
@click="classWorkAnalysis.view = 'studentview'"
|
||||||
|
>作业批阅</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-if="classWorkAnalysis.row.worktype == '习题训练'"
|
||||||
|
:type="classWorkAnalysis.view == 'quizStats' ? 'success' : ''"
|
||||||
|
@click="workHandle('quizStats')"
|
||||||
|
>作业概况</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-if="classWorkAnalysis.row.worktype == '习题训练'"
|
||||||
|
:type="classWorkAnalysis.view == 'report' ? 'success' : ''"
|
||||||
|
@click="handleClassOverviewOpen('report')"
|
||||||
|
>作业报告</el-button
|
||||||
|
>
|
||||||
|
</el-button-group>
|
||||||
|
</el-row>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div
|
||||||
|
style="float: right; padding: 0 10px; cursor: pointer"
|
||||||
|
icon="el-icon-close"
|
||||||
|
@click="closeDialog"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 如果当前学习没有试题 :height="mainHeight"-->
|
||||||
|
<div
|
||||||
|
v-if="classWorkAnalysis.view == 'studentview'"
|
||||||
|
style="width: 100%; height:75vh; "
|
||||||
|
class="clwk_dialog_view"
|
||||||
|
>
|
||||||
|
<div class="view_table">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="tableRadio.value"
|
||||||
|
style="margin-bottom: 1px"
|
||||||
|
@change="tableRadioChange"
|
||||||
|
>
|
||||||
|
<el-radio-button :value="1" :label="'已交' + '(' + tableRadio.num1 + ')'" />
|
||||||
|
<el-radio-button :value="0" :label="'未交' + '(' + tableRadio.num0 + ')'" />
|
||||||
|
</el-radio-group>
|
||||||
|
<!-- 学生列表:classWorkAnalysis.classworkdata; 已交未交:tableRadio.list -->
|
||||||
|
<el-table
|
||||||
|
v-loading="loading_dt_table"
|
||||||
|
:data="tableRadio.list"
|
||||||
|
row-key="id"
|
||||||
|
style="height: 69vh;"
|
||||||
|
highlight-current-row
|
||||||
|
@row-click="getStudentClassWorkDataDetail"
|
||||||
|
>
|
||||||
|
<el-table-column type="index" label="序号" width="52" reserve-selection align="center" />
|
||||||
|
<el-table-column label="姓名" prop="studentname" width="100" align="center" />
|
||||||
|
<el-table-column label="提交时间" prop="updatedate" width="170" align="center" />
|
||||||
|
<el-table-column label="批阅状态" prop="teacherRating" align="center" width="120" sortable>
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.teacherRating == 0"
|
||||||
|
><span style="color: #2196f3">待批阅</span></template
|
||||||
|
>
|
||||||
|
<!-- 1-优 2-优减 3-良 4-良减 5-差 -->
|
||||||
|
<template v-if="scope.row.teacherRating == 1"
|
||||||
|
><el-tag type="danger">优</el-tag></template
|
||||||
|
>
|
||||||
|
<template v-if="scope.row.teacherRating == 2"
|
||||||
|
><el-tag type="danger">优-</el-tag></template
|
||||||
|
>
|
||||||
|
<template v-if="scope.row.teacherRating == 3"
|
||||||
|
><el-tag type="warning">良</el-tag></template
|
||||||
|
>
|
||||||
|
<template v-if="scope.row.teacherRating == 4"
|
||||||
|
><el-tag type="info">良-</el-tag></template
|
||||||
|
>
|
||||||
|
<template v-if="scope.row.teacherRating == 5"
|
||||||
|
><el-tag type="info">差</el-tag></template
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="view_teachrting">
|
||||||
|
<div class="classwork-score">
|
||||||
|
<div v-if="classWorkAnalysis.activeStudentQuizlist.length == 0">
|
||||||
|
<el-empty
|
||||||
|
description="点击左侧表格学生信息可查看批阅详情"
|
||||||
|
style="width: 100%; height: 500px"
|
||||||
|
></el-empty>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="isopen_dtwk_table">
|
||||||
|
<div v-show="classWorkAnalysis.activeStudentQuizlist.length > 0">
|
||||||
|
<item-dialog-score
|
||||||
|
ref="classWorkAnalysisScoreDialogRef"
|
||||||
|
@class_work_score_submit="onClassWorkScoreSubmit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-empty
|
||||||
|
description="点击左侧表格学生信息可查看批阅详情"
|
||||||
|
style="width: 100%; height: 500px"
|
||||||
|
></el-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作业概况 -->
|
||||||
|
<div v-else-if="classWorkAnalysis.view == 'quizStats'">
|
||||||
|
<quiz-stats :active-data="classWorkActiveData" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作业报告-->
|
||||||
|
<div v-else-if="classWorkAnalysis.view == 'report'" style="overflow-y: scroll">
|
||||||
|
<!-- <ClassOverview :table-list="overviewData" :eval-id="courseObj.evalid"></ClassOverview> -->
|
||||||
|
<ClassOverview :table-list="overviewData"></ClassOverview>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="classWorkAnalysis.open=false">关 闭</el-button>
|
||||||
|
</div>
|
||||||
|
</template> -->
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup name="itemDialogRef">
|
||||||
|
import { ref, defineExpose, onMounted, reactive, computed, watch, onUnmounted, nextTick, getCurrentInstance } from 'vue'
|
||||||
|
import { addSmartClassReserv, updateSmartClassReserv, listClassmain } from '@/api/classManage'
|
||||||
|
import { listClassworkdata, listEntpcoursework, listClassworkeval } from '@/api/classTask'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { getCurrentTime, getAfterMinutes } from '@/utils/date'
|
||||||
|
import { processList } from '@/hooks/useProcessList'
|
||||||
|
import ItemDialogScore from '@/views/classTask/container/item-dialog-score.vue'
|
||||||
|
// zdg: 组件导入
|
||||||
|
import quizStats from '@/views/classTask/container/quizStats.vue'
|
||||||
|
import ClassOverview from '@/views/classTask/container/classOverview.vue'
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance()
|
||||||
|
const emit = defineEmits(['cle-click'])
|
||||||
|
const props = defineProps({
|
||||||
|
bookId: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const mainHeight = ref(document.documentElement.clientHeight - 110)
|
||||||
|
const classWorkAnalysis = reactive({
|
||||||
|
open: false
|
||||||
|
})
|
||||||
|
const tableRadio = reactive({
|
||||||
|
value: '1', // 已交
|
||||||
|
list: [], // 已交list
|
||||||
|
num1: 0, // 已交人数
|
||||||
|
num0: 0 // 未交人数
|
||||||
|
}) // 批阅 是否已交
|
||||||
|
const loading_dt_table = ref(false)
|
||||||
|
const isopen_dtwk_table = ref(false)
|
||||||
|
|
||||||
|
// zdg: 作业概况
|
||||||
|
const classWorkActiveData = reactive({
|
||||||
|
quizlist: [], // 当前习题列表
|
||||||
|
studentList: [], // 当前课程-所有学生
|
||||||
|
workFeedList: [], // 当前课程-所有学生反馈数据
|
||||||
|
timerId: 0 // 定时器id
|
||||||
|
})
|
||||||
|
//所选学生题目- 答题题目分析与评价
|
||||||
|
const classWorkAnalysisScore = reactive({
|
||||||
|
studentObj: {}, // 当前学生的作业题型等信息
|
||||||
|
studentQuizAllList: [], // 选择学生的回答list
|
||||||
|
quizlist: [] // 选择学生的题目list
|
||||||
|
})
|
||||||
|
|
||||||
|
//查看学生概况数据
|
||||||
|
const overviewData = ref([])
|
||||||
|
|
||||||
|
// watch(
|
||||||
|
// // () => props.currentNode,
|
||||||
|
// (newValue, oldValue) => {
|
||||||
|
// form.name = newValue.label
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
const openDialog = (data) => {
|
||||||
|
console.log(data, '点击的item答题情况')
|
||||||
|
|
||||||
|
classWorkAnalysis.title = data.uniquekey ? data.uniquekey + '--' : ''
|
||||||
|
classWorkAnalysis.worktype = data.worktype
|
||||||
|
classWorkAnalysis.workclass = data.workclass
|
||||||
|
// 重置学生列表
|
||||||
|
tableRadio.list = []
|
||||||
|
tableRadio.value = '1'
|
||||||
|
tableRadio.num0 = 0
|
||||||
|
tableRadio.num1 = 0
|
||||||
|
|
||||||
|
classWorkAnalysis.open = true
|
||||||
|
// 默认显示是学生作业反馈
|
||||||
|
classWorkAnalysis.view = 'studentview'
|
||||||
|
// 当前学习任务所包含的试题ID
|
||||||
|
classWorkAnalysis.entpcourseworklistarray = data.entpcourseworklistarray
|
||||||
|
// 默认选中的学生
|
||||||
|
classWorkAnalysis.activeStudentQuizlist = []
|
||||||
|
// 默认选中的试题
|
||||||
|
classWorkAnalysis.activeQuizAnalysisData = []
|
||||||
|
|
||||||
|
classWorkAnalysis.row = data
|
||||||
|
window.test = this
|
||||||
|
// zdg: 学生列表
|
||||||
|
const studentArr = data.classworkdatastudentids
|
||||||
|
? JSON.parse(`[${data.classworkdatastudentids}]`)
|
||||||
|
: []
|
||||||
|
classWorkActiveData.studentList = studentArr
|
||||||
|
/** 学生完成情况分析--获取作业学生list数据 */
|
||||||
|
getClassWorkStudentList(data.id)
|
||||||
|
|
||||||
|
// 课程列表idlist
|
||||||
|
var ids = []
|
||||||
|
for (var i = 0; i < data.entpcourseworklistarray.length; i++) {
|
||||||
|
ids.push(data.entpcourseworklistarray[i].id)
|
||||||
|
}
|
||||||
|
// 课程作业列表
|
||||||
|
listEntpcoursework({ ids: ids.join(','), pageSize: 500 }).then((idres) => {
|
||||||
|
for (var i = 0; i < idres.rows.length; i++) {
|
||||||
|
// //新增了 复合题、主观题(背景+小题目) 题目标题优化一下 .replace(/!@#\$%/g,'')
|
||||||
|
idres.rows[i].titletext = idres.rows[i].title.replace(/!@#\$%/g, '')
|
||||||
|
}
|
||||||
|
classWorkAnalysis.quizlist = idres.rows
|
||||||
|
classWorkActiveData.quizlist = idres.rows // zdg: 作业概览组件使用
|
||||||
|
|
||||||
|
// 统计每个题目的正误率
|
||||||
|
// 这个学习任务所有题目+所有学生的答题数据 , pageSize: 100
|
||||||
|
listClassworkeval({ workid: data.id, pageSize: 1000 }).then((wevalres) => {
|
||||||
|
for (var i = 0; i < classWorkAnalysis.quizlist.length; i++) {
|
||||||
|
// 分析每一道题目
|
||||||
|
var scoingCount = 0
|
||||||
|
var feedcount = 0
|
||||||
|
// 全部人数
|
||||||
|
var evalCount = 0
|
||||||
|
for (var w = 0; w < wevalres.rows.length; w++) {
|
||||||
|
if (wevalres.rows[w].entpcourseworkid == classWorkAnalysis.quizlist[i].id) {
|
||||||
|
evalCount++
|
||||||
|
|
||||||
|
// 只统计有回答的题目
|
||||||
|
if (wevalres.rows[w].feedcontent != '') {
|
||||||
|
// 已经作答的人数
|
||||||
|
feedcount++
|
||||||
|
// 简单判断正误
|
||||||
|
if (wevalres.rows[w].feedcontent == wevalres.rows[w].rightanswer) {
|
||||||
|
wevalres.rows[w].scoingStatus = true
|
||||||
|
scoingCount++
|
||||||
|
// 学生回答与参考答案一样,则:得分=分值
|
||||||
|
wevalres.rows[w].teacherRating = wevalres.rows[w].score
|
||||||
|
} else {
|
||||||
|
wevalres.rows[w].scoingStatus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classWorkAnalysis.quizlist[i].evalCount = evalCount
|
||||||
|
// 作答人数
|
||||||
|
classWorkAnalysis.quizlist[i].feedcount = feedcount
|
||||||
|
// 得分率 这里优化一下(没回答会是NaN%) scoingRate
|
||||||
|
if (scoingCount == 0 && feedcount == 0) {
|
||||||
|
classWorkAnalysis.quizlist[i].scoingRate = '0%'
|
||||||
|
} else {
|
||||||
|
classWorkAnalysis.quizlist[i].scoingRate =
|
||||||
|
((scoingCount / feedcount) * 100).toFixed(0) + '%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// zdg: 所有反馈数据
|
||||||
|
const getStudentid = (workdataid) => {
|
||||||
|
// 获取学生id
|
||||||
|
const classworkdata = (classWorkAnalysis.classworkdata || []).find(
|
||||||
|
(o) => o.id === workdataid
|
||||||
|
)
|
||||||
|
return classworkdata ? classworkdata.studentid : ''
|
||||||
|
}
|
||||||
|
wevalres.rows.forEach((o) => {
|
||||||
|
o.studentid = getStudentid(o.workdataid)
|
||||||
|
})
|
||||||
|
classWorkActiveData.workFeedList = wevalres.rows
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(classWorkAnalysis, '点击进度后获得的数据')
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region 学生完成情况分析
|
||||||
|
/** 1、获取作业学生列表 */
|
||||||
|
const getClassWorkStudentList = (rowId) => {
|
||||||
|
// 本地保存这个rowid,老师批改后续中使用
|
||||||
|
localStorage.setItem('activeClassWorkRowId', rowId)
|
||||||
|
// 先清空表格中的数据
|
||||||
|
classWorkAnalysis.classworkdata = []
|
||||||
|
// 加载中_
|
||||||
|
loading_dt_table.value = true
|
||||||
|
// 找当前学习任务的classworkdata数据
|
||||||
|
listClassworkdata({ classworkid: rowId, pageSize: 100 })
|
||||||
|
.then((response) => {
|
||||||
|
for (var i = 0; i < response.rows.length; i++) {
|
||||||
|
if (response.rows[i].entpcourseworklist != '') {
|
||||||
|
response.rows[i].entpcourseworkarray = JSON.parse(
|
||||||
|
'[' + response.rows[i].entpcourseworklist + ']'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
response.rows[i].entpcourseworkarray = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 老师批阅状态 默认0 未批改
|
||||||
|
response.rows[i].teacherRating = 0
|
||||||
|
|
||||||
|
// 计算每个学生的得分率
|
||||||
|
if (
|
||||||
|
response.rows[i].classworkevallist != '' &&
|
||||||
|
response.rows[i].classworkevallist != null &&
|
||||||
|
response.rows[i].classworkevallist != 'null'
|
||||||
|
) {
|
||||||
|
// 将标签中双引号改为转义, 测试数据: "{\"id\":172910, \"feedcontent\":\"毛泽东,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\", \"score\":4, \"rightanswer\":\"毛泽东重,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\"},{\"id\":172911, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":363100, \"feedcontent\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\", \"score\":4, \"rightanswer\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\"}"
|
||||||
|
// 常规作业(去除【】前后引号).replace(/"(\[.*\])"/g, '$1'); :eg: "feedcontent\":\"[{\"name\":\"Bliss.jpg\",\"url\":\"https://wzyzoss.3b8daa474.jpg\"}]\",
|
||||||
|
// json转换会报错; .replace(/""/g, '"') eg: ""宇宙环境安全""
|
||||||
|
response.rows[i].classworkevallist = escapeHtmlQuotes(response.rows[i].classworkevallist)
|
||||||
|
console.log('学生完成情况分析classworkevallist', response.rows[i].classworkevallist)
|
||||||
|
const evalarray = JSON.parse('[' + response.rows[i].classworkevallist + ']')
|
||||||
|
var scoingCount = 0
|
||||||
|
var feedcount = 0
|
||||||
|
for (var e = 0; e < evalarray.length; e++) {
|
||||||
|
if (evalarray[e].feedcontent != '') {
|
||||||
|
feedcount++
|
||||||
|
// 与答案对比正误。注意注意,这里仅限单选题
|
||||||
|
if (evalarray[e].feedcontent == evalarray[e].rightanswer) {
|
||||||
|
scoingCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(evalarray, 'evalarray------------------------------------')
|
||||||
|
if (feedcount > 0) {
|
||||||
|
// 多个题目的总得分率: 正确题数/(题目数*100)
|
||||||
|
response.rows[i].scoingRate = ((scoingCount / feedcount) * 100).toFixed(0) + '%'
|
||||||
|
} else {
|
||||||
|
response.rows[i].scoingRate = '0%'
|
||||||
|
}
|
||||||
|
// 批阅状态 优良类 :注意:这里题目中的评价都是一样的,所以取第一个
|
||||||
|
if (evalarray[0].rating != '') {
|
||||||
|
response.rows[i].teacherRating = evalarray[0].rating
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.rows[i].scoingRate = '0%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classWorkAnalysis.classworkdata = response.rows
|
||||||
|
loading_dt_table.value = false
|
||||||
|
|
||||||
|
// 默认获取已交的学生列表
|
||||||
|
tableRadio.list =
|
||||||
|
classWorkAnalysis.classworkdata &&
|
||||||
|
classWorkAnalysis.classworkdata.filter((item) => item.resultcount > 0)
|
||||||
|
tableRadio.value = '1'
|
||||||
|
tableRadio.num0 = classWorkAnalysis.classworkdata.length - tableRadio.list.length
|
||||||
|
tableRadio.num1 = tableRadio.list.length
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading_dt_table.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/** 2、查看某一个学生的学习任务完成详情*/
|
||||||
|
const getStudentClassWorkDataDetail = (row) => {
|
||||||
|
// 这里取出的是学生完成情况
|
||||||
|
// 具体的题目数据在this.classWorkAnalysis.quizlist里
|
||||||
|
console.log(row, '点击了左侧学生')
|
||||||
|
//更新到待批改的学生信息中
|
||||||
|
classWorkAnalysisScore.studentObj = row
|
||||||
|
listClassworkeval({ workdataid: row.id, pageSize: 100 })
|
||||||
|
.then((wevalres) => {
|
||||||
|
for (var i = 0; i < classWorkAnalysis.quizlist.length; i++) {
|
||||||
|
// 分析每一道题目
|
||||||
|
for (var w = 0; w < wevalres.rows.length; w++) {
|
||||||
|
if (wevalres.rows[w].entpcourseworkid == classWorkAnalysis.quizlist[i].id) {
|
||||||
|
wevalres.rows[w].quiztitle = classWorkAnalysis.quizlist[i].title
|
||||||
|
wevalres.rows[w].quiztitletext = classWorkAnalysis.quizlist[i].title.replace(
|
||||||
|
/<[^>]*>/g,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
wevalres.rows[w].score = wevalres.rows[w].score ? wevalres.rows[w].score : 0
|
||||||
|
|
||||||
|
// 参考答案 去除下html标签
|
||||||
|
wevalres.rows[w].rightanswer =
|
||||||
|
wevalres.rows[w].rightanswer != '' && wevalres.rows[w].rightanswer != null
|
||||||
|
? wevalres.rows[w].rightanswer.replace(/<[^>]+>/g, '')
|
||||||
|
: wevalres.rows[w].rightanswer
|
||||||
|
// 学生回答 去除下html标签
|
||||||
|
wevalres.rows[w].feedcontent =
|
||||||
|
wevalres.rows[w].feedcontent != '' && wevalres.rows[w].feedcontent != null
|
||||||
|
? wevalres.rows[w].feedcontent.replace(/<[^>]+>/g, '')
|
||||||
|
: wevalres.rows[w].feedcontent
|
||||||
|
|
||||||
|
if (classWorkAnalysis.row.worktype == '常规作业') {
|
||||||
|
wevalres.rows[w].feedcontent = JSON.parse(wevalres.rows[w].feedcontent)
|
||||||
|
}
|
||||||
|
if (wevalres.rows[w].feedcontent != '') {
|
||||||
|
if (wevalres.rows[w].feedcontent == wevalres.rows[w].rightanswer) {
|
||||||
|
wevalres.rows[w].scoingStatus = true
|
||||||
|
// 学生回答与参考答案一样,则:得分=分值
|
||||||
|
wevalres.rows[w].teacherRating = wevalres.rows[w].score
|
||||||
|
} else {
|
||||||
|
wevalres.rows[w].scoingStatus = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wevalres.rows[w].scoingStatus = ''
|
||||||
|
}
|
||||||
|
// 计算得分
|
||||||
|
}
|
||||||
|
// "回答" prop="feedcontent" width="200" align="center"></el-table-column>
|
||||||
|
// <el-table-column label="参考答案" prop="rightanswer"
|
||||||
|
//新增了 复合题、主观题(背景+小题目) 题目标题优化一下
|
||||||
|
wevalres.rows[w].worktitle = wevalres.rows[w].worktitle.replace(/!@#\$%/g, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classWorkAnalysis.activeStudentQuizlist = wevalres.rows
|
||||||
|
// 加载右边表格
|
||||||
|
isopen_dtwk_table.value = true
|
||||||
|
// 加载右边评语区域
|
||||||
|
if (wevalres.rows.length > 0) {
|
||||||
|
handleClassWorkAnalysissScoreOpen(row)
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'warning',
|
||||||
|
message: '未获取到答题信息,请稍后再看,或者联系管理员查看情况!'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log('获取答题情况失败')
|
||||||
|
ElMessage({
|
||||||
|
type: 'warning',
|
||||||
|
message: '未获取到答题信息!'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/** 3、教师批改后返回的方法*/
|
||||||
|
const onClassWorkScoreSubmit = () => {
|
||||||
|
console.log('批改后返回的方法')
|
||||||
|
loading_dt_table.value = true
|
||||||
|
isopen_dtwk_table.value = false
|
||||||
|
// 1、清空答题情况的两个table数据,- 左侧学生列表:classWorkAnalysis.classworkdata;- 右侧学生回答题目列表:classWorkAnalysis.activeStudentQuizlist
|
||||||
|
// - 左侧学生列表
|
||||||
|
classWorkAnalysis.classworkdata = []
|
||||||
|
classWorkAnalysis.activeStudentQuizlist = []
|
||||||
|
// 2、刷新左侧学生列表数据,更新批改状态
|
||||||
|
const rowid = localStorage.getItem('activeClassWorkRowId')
|
||||||
|
getClassWorkStudentList(rowid)
|
||||||
|
}
|
||||||
|
// 查看并批改一个学生的题目作答(答题题目分析与评价)
|
||||||
|
const handleClassWorkAnalysissScoreOpen = (row) => {
|
||||||
|
console.log(row, '所选点击的信息')
|
||||||
|
|
||||||
|
// 当前学生的回答list
|
||||||
|
classWorkAnalysisScore.studentQuizAllList = classWorkAnalysis.activeStudentQuizlist
|
||||||
|
// 当前学生的所有题目list
|
||||||
|
classWorkAnalysisScore.quizlist = classWorkAnalysis.quizlist
|
||||||
|
// 格式化试题格式信息
|
||||||
|
processList(classWorkAnalysisScore.quizlist)
|
||||||
|
// 屏幕高度
|
||||||
|
classWorkAnalysisScore.maxheight = mainHeight.value - 100
|
||||||
|
|
||||||
|
// 防止组件未渲染完成 调用里面方法报错
|
||||||
|
nextTick(() => {
|
||||||
|
proxy.$refs.classWorkAnalysisScoreDialogRef.acceptParams(classWorkAnalysisScore)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
/** 批阅:已交未交事件 */
|
||||||
|
const tableRadioChange = (e) => {
|
||||||
|
// 关闭右侧批阅ui
|
||||||
|
isopen_dtwk_table.value = false;
|
||||||
|
console.log(e,'??????')
|
||||||
|
console.log("学生列表:", classWorkAnalysis.classworkdata)
|
||||||
|
if(e=='1'){
|
||||||
|
tableRadio.list = classWorkAnalysis.classworkdata.filter(item => item.resultcount > 0)
|
||||||
|
tableRadio.value = '1';
|
||||||
|
tableRadio.num0 = classWorkAnalysis.classworkdata.length - tableRadio.list.length;
|
||||||
|
tableRadio.num1 = tableRadio.list.length;
|
||||||
|
}else if(e=='0'){
|
||||||
|
tableRadio.list = classWorkAnalysis.classworkdata.filter(item => item.resultcount == 0)
|
||||||
|
tableRadio.value = '0';
|
||||||
|
tableRadio.num0 = tableRadio.list.length;
|
||||||
|
tableRadio.num1 = classWorkAnalysis.classworkdata.length - tableRadio.list.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将标签中的双引号增加转义
|
||||||
|
const escapeHtmlQuotes = (str) => {
|
||||||
|
// 后端已replace双引号, 故前端不用在处理
|
||||||
|
return str
|
||||||
|
|
||||||
|
// 只对双引号进行转义
|
||||||
|
// return str.replace(/(<[^>]+>)/g, function (match) {
|
||||||
|
// return match.replace(/"/g, '\\"')
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region 作业概况
|
||||||
|
// 查看学生-作业概览
|
||||||
|
const workHandle = (type) => {
|
||||||
|
// 关闭右侧批阅ui
|
||||||
|
isopen_dtwk_table.value = false;
|
||||||
|
classWorkAnalysis.view = type
|
||||||
|
const isClose = type != 'quizStats' && !! classWorkActiveData.timerId
|
||||||
|
const isOpen = type == 'quizStats' && !classWorkActiveData.timerId
|
||||||
|
if (isClose) clearInterval(classWorkActiveData.timerId) // 关闭定时器
|
||||||
|
if (isOpen) {
|
||||||
|
// 轮询 更新学生作答数据
|
||||||
|
classWorkActiveData.timerId = setInterval(() => {
|
||||||
|
console.log('zdg: 定时执行')
|
||||||
|
getWorkFeedList()
|
||||||
|
}, 20 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 获取学生答题回馈数据-更新
|
||||||
|
const getWorkFeedList = async() =>{
|
||||||
|
const workid = classWorkAnalysis.row.id
|
||||||
|
const res = await listClassworkeval({workid, isFinish: 1, pageSize: 1000})
|
||||||
|
const getStudentid = (workdataid) => { // 获取学生id
|
||||||
|
const classworkdata = (classWorkAnalysis.classworkdata||[]).find(o => o.id === workdataid)
|
||||||
|
return classworkdata ? classworkdata.studentid : ''
|
||||||
|
}
|
||||||
|
res.rows.forEach(o => { o.studentid = getStudentid(o.workdataid) })
|
||||||
|
classWorkActiveData.workFeedList = res.rows
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
|
||||||
|
//#regin 作业报告
|
||||||
|
/*
|
||||||
|
author: yangws
|
||||||
|
time: 2024-8-06 16:35:33
|
||||||
|
function:作业报告的处理
|
||||||
|
*/
|
||||||
|
const handleClassOverviewOpen = (type) =>{
|
||||||
|
// 关闭右侧批阅ui
|
||||||
|
isopen_dtwk_table.value = false;
|
||||||
|
classWorkAnalysis.view = type
|
||||||
|
const data = classWorkAnalysis.row
|
||||||
|
//获取所有学生列表
|
||||||
|
listClassworkdata({classworkid: data.id, pageSize: 100}).then((response) => {
|
||||||
|
if(response.code === 200){
|
||||||
|
response.rows.forEach(item => {
|
||||||
|
let rightAnswer = 0
|
||||||
|
let answers = 0
|
||||||
|
if(!item.classworkevallist) return
|
||||||
|
// 使用正则表达式替换字符串值中的双引号为单引号
|
||||||
|
let replacedString = item.classworkevallist.replace(/""/g, "\"");
|
||||||
|
// 将标签中双引号改为转义, 测试数据: "{\"id\":172907, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":358520, \"feedcontent\":\"④①⑤③②\", \"score\":4, \"rightanswer\":\"④①⑤③②\"},{\"id\":172908, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":358521, \"feedcontent\":\"气壮山威,鲲鹏展翅楚云飞\", \"score\":4, \"rightanswer\":\"志远天高,春风杨柳麓山青\"},{\"id\":172909, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":363096, \"feedcontent\":\"《红烛》化用“蜡矩”这一古典意象,赋予它新的含义,赞美了红烛以“蜡炬成灰”来点亮世界的奉献精神。\", \"score\":4, \"rightanswer\":\"《立在地球边上放号》中,全诗采用间接抒情的方式,描绘了太平洋的浪潮,吟唱了一曲惊心动魄的力的颂歌,意在赞美摧毁旧世界、创造新生活的“五四”精神。\"},{\"id\":172910, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":363098, \"feedcontent\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\", \"score\":4, \"rightanswer\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\"},{\"id\":172911, \"rating\":0, \"teacherRating\":0, \"entpcourseworkid\":363100, \"feedcontent\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\", \"score\":4, \"rightanswer\":\"毛泽东重游橘子洲,面对如画的秋色和大好的革命形势,回忆过去战斗的岁月,不禁心潮起伏,<bdo class=\"mathjye-underpoint2\">浮想联翩</bdo>。\"}"
|
||||||
|
replacedString = escapeHtmlQuotes(item.classworkevallist);
|
||||||
|
let allTopic
|
||||||
|
try{
|
||||||
|
allTopic = JSON.parse(`[${item.classworkevallist}]`)
|
||||||
|
}catch{
|
||||||
|
allTopic = JSON.parse(`[${replacedString}]`)
|
||||||
|
}
|
||||||
|
if(item.classworkevallist != ''){
|
||||||
|
allTopic.forEach(itemTopic => {
|
||||||
|
if(itemTopic.feedcontent != ''){
|
||||||
|
answers ++
|
||||||
|
//正确答案,仅限单选题
|
||||||
|
if(itemTopic.feedcontent === itemTopic.rightanswer){
|
||||||
|
rightAnswer ++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
rightAnswer > 0?item.scoingRate = (rightAnswer/answers * 100).toFixed(0):item.scoingRate = ''
|
||||||
|
}else{
|
||||||
|
item.scoingRate = ''
|
||||||
|
}
|
||||||
|
//获得总分
|
||||||
|
const point = allTopic.reduce((acc, cur) => {
|
||||||
|
if(cur.rating !== 0){
|
||||||
|
return acc + cur.teacherRating;
|
||||||
|
}
|
||||||
|
},0)
|
||||||
|
// item.chapter = this.courseObj.evalid
|
||||||
|
item.point = point || 0
|
||||||
|
item.rating = allTopic[0].rating
|
||||||
|
|
||||||
|
})
|
||||||
|
overviewData.value = [...response.rows]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
const onBeforeClose = () =>{
|
||||||
|
console.log('非正常关闭dialog?esc、dialog外部区域')
|
||||||
|
closeDialog()
|
||||||
|
}
|
||||||
|
const closeDialog = () => {
|
||||||
|
classWorkAnalysis.open = false
|
||||||
|
emit('cle-click')
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(classWorkAnalysis, (newVal, oldVal) => {
|
||||||
|
if(newVal.view != 'quizStats'){
|
||||||
|
console.log('关闭zdg: 定时执行')
|
||||||
|
clearInterval(classWorkActiveData.timerId) // 关闭定时器 作业概况的
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(classWorkActiveData.timerId) // 关闭定时器 作业概况的
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openDialog,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
// :deep(.reserv-date-pick) {
|
||||||
|
// width: 140px;
|
||||||
|
// }
|
||||||
|
// :deep(.reserv-time-pick) {
|
||||||
|
// width: 240px;
|
||||||
|
// }
|
||||||
|
.clwk_dialog {
|
||||||
|
.clwk_dialog_view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
// align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.view_table {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.view_teachrting {
|
||||||
|
flex: 2;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clwk_dialog {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.clwk_dialog .el-dialog {
|
||||||
|
margin: 0 auto !important;
|
||||||
|
height: 85%!important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.clwk_dialog .el-dialog__header {
|
||||||
|
/* position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0; */
|
||||||
|
width: 100%!important;
|
||||||
|
}
|
||||||
|
.clwk_dialog .el-dialog__body {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 15px;
|
||||||
|
bottom: 1px;
|
||||||
|
right:0;
|
||||||
|
padding:5px;
|
||||||
|
z-index:1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
/* overflow:hidden;
|
||||||
|
overflow-y: auto; */
|
||||||
|
}
|
||||||
|
.clwk_dialog .el-dialog__footer{
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
.clwk_dialog .classwork-score{
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.clwk_dialog {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.clwk_dialog .el-dialog {
|
||||||
|
margin: 0 auto !important;
|
||||||
|
height: 85%!important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.clwk_dialog .el-dialog__header {
|
||||||
|
/* position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0; */
|
||||||
|
width: 100%!important;
|
||||||
|
}
|
||||||
|
.clwk_dialog .el-dialog__body {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 15px;
|
||||||
|
bottom: 1px;
|
||||||
|
right:0;
|
||||||
|
padding:5px;
|
||||||
|
z-index:1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
/* overflow:hidden;
|
||||||
|
overflow-y: auto; */
|
||||||
|
}
|
||||||
|
.clwk_dialog .el-dialog__footer{
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
.clwk_dialog .classwork-score{
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,279 @@
|
||||||
|
<template>
|
||||||
|
<el-row class="c-warp" :gutter="10">
|
||||||
|
<el-col class="left" :span="16">
|
||||||
|
<el-collapse class="c-item" v-model="activeTopic" accordion>
|
||||||
|
<template v-for="(item, index) in dataList">
|
||||||
|
<el-collapse-item class="collapse-item" :name="index+1" :id="'collapse-'+(index+1)">
|
||||||
|
<template #title>
|
||||||
|
<el-popover :width="500" placement="right">
|
||||||
|
<p>{{item.def?.titletext}}</p>
|
||||||
|
<template #reference>
|
||||||
|
<el-button type="primary" size="small" round>{{index+1}}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
<span class="item-title-o">{{item.type}} {{getRatioTxt(item)}} </span>
|
||||||
|
<el-tag type="success" size="small">{{item.points}}%</el-tag>
|
||||||
|
</template>
|
||||||
|
<div class="respond">
|
||||||
|
<div class="c-label">
|
||||||
|
<b t1>作答情况</b>
|
||||||
|
<span>(已经完成 <el-text type="danger">{{item.accSum}}</el-text> 人)</span>
|
||||||
|
</div>
|
||||||
|
<div class="c-childen">
|
||||||
|
<template v-for="(it, ind) in item.children">
|
||||||
|
<el-collapse v-model="item.active">
|
||||||
|
<el-collapse-item class="collapse-item" :name="ind+1">
|
||||||
|
<template #title>
|
||||||
|
<div class="t-left">
|
||||||
|
<el-tooltip placement="right" :content="it.def">
|
||||||
|
<el-tag size="small" style="vertical-align: 2px;" v-html="it.code"></el-tag>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-text t1>{{it.studentIds.length}} 人/占 {{ratio_1(it, item.accSum)}}%</el-text>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<el-progress :status="getStatus(it)" :stroke-width="10" :percentage="ratio_1(it, item.accSum)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="c-respond">
|
||||||
|
<template v-for="(sid, indStu) in it.studentIds">
|
||||||
|
<el-tag>{{getStudentName(sid)}}</el-tag>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</template>
|
||||||
|
</el-collapse>
|
||||||
|
</el-col>
|
||||||
|
<el-col class="right" :span="8">
|
||||||
|
<div class="c-item">
|
||||||
|
<div class="title">答题情况</div>
|
||||||
|
<div class="respond">
|
||||||
|
<el-space wrap>
|
||||||
|
<!-- <template v-for="it in 11"> -->
|
||||||
|
<template v-for="(item, index) in dataList">
|
||||||
|
<el-card shadow="hover" class="card-warp">
|
||||||
|
<div class="card-body">
|
||||||
|
<el-progress type="dashboard" :color="colorArr" :width="80" :percentage="ratio_2(item)" />
|
||||||
|
<el-button type="primary" :plain="getActive(index+1)" size="small" round
|
||||||
|
@click="clickInfo(index+1)">{{index+1}}</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
</el-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineExpose, onMounted, reactive, computed, watch, nextTick, watchEffect } from 'vue'
|
||||||
|
// 功能说明:习题-概况|作业统计(使用:公屏-习题练习|作业管理-作业布置)
|
||||||
|
// === 引入和参数初始化 ===
|
||||||
|
// import { nextTick } from 'vue'
|
||||||
|
|
||||||
|
// import * as elementPlus from 'element-plus' // ElMessage ElMessageBox
|
||||||
|
let colorArr = [] // 进度颜色值 -- 静态数据
|
||||||
|
// const attrs = useAttrs() // props中未定义属性
|
||||||
|
const activeTopic = ref(0) // 展开的题
|
||||||
|
let dataList = ref([]) // 左侧数据
|
||||||
|
let studentList = ref([]) // 学生数据
|
||||||
|
const props = defineProps({ // 参数 defineProps
|
||||||
|
activeData: { // 数据
|
||||||
|
type: Object,
|
||||||
|
// required: true, // 必传
|
||||||
|
default: () => ({
|
||||||
|
quizlist: [], // 当前习题列表
|
||||||
|
studentList: [], // 当前课程-所有学生
|
||||||
|
workFeedList: [] // 当前课程-所有学生反馈数据
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// 左侧数据
|
||||||
|
// dataList.value = [
|
||||||
|
// { id: 1, type: '单选题', points: '47.5', accSum: 18, active: [], children: [
|
||||||
|
// { code: 'A', isOk: false, studentIds: [55056, 55057, 55058]},
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
// 颜色值
|
||||||
|
colorArr = [
|
||||||
|
{ color: '#f56c6c', percentage: 20 },
|
||||||
|
{ color: '#e6a23c', percentage: 50 },
|
||||||
|
{ color: '#1989fa', percentage: 80 },
|
||||||
|
{ color: '#5cb87a', percentage: 100 },
|
||||||
|
]
|
||||||
|
|
||||||
|
// === 初始加载完 ===
|
||||||
|
onMounted(() => {})
|
||||||
|
|
||||||
|
// === 方法(methods) ===
|
||||||
|
// 初始-数据处理
|
||||||
|
const initData = () => {
|
||||||
|
// console.log('xxx', props)
|
||||||
|
// window.test = activeCourse
|
||||||
|
studentList.value = props.activeData.studentList || []
|
||||||
|
const activeWorkFeedList = props.activeData.workFeedList || []
|
||||||
|
const quizlist = props.activeData.quizlist || []
|
||||||
|
// 习题特殊处理
|
||||||
|
let data = quizlist.map(o => {
|
||||||
|
// 解析题选项
|
||||||
|
const workdesc = o.workdesc || ''
|
||||||
|
let accSum = 0 // 该题总人数
|
||||||
|
let activeIds = [] // 已做答学生
|
||||||
|
const quizFeedList = activeWorkFeedList.filter(f => f.entpcourseworkid == o.id) // 做该题的列表
|
||||||
|
let children = []
|
||||||
|
if (['单选题','多选题'].includes(o.worktype)) { // '单选题','多选题'
|
||||||
|
const list = workdesc.includes('#&') ? workdesc.split('#&') : isJson(workdesc)?JSON.parse(workdesc):[]
|
||||||
|
children = list.map((v,i) => {
|
||||||
|
const isOne = o.worktype == '单选题'
|
||||||
|
const code = toCode(i) // 转换 A-Z
|
||||||
|
// const isOk = isOne ? i == o.workanswer : o.workanswer.includes(i) // 是否(包含)正确答案
|
||||||
|
const isOk = (isJson(workdesc)?JSON.parse(o.workanswer):o.workanswer||'').includes(i+'') // 是否(包含)正确答案
|
||||||
|
// 改选项的学生id
|
||||||
|
const studentIds = quizFeedList.filter(f => isOne ? f.feedcontent==v : f.feedcontent.includes(i)).map(f => f.studentid)||[]
|
||||||
|
accSum += studentIds.length
|
||||||
|
if(isOk) isOne ? activeIds.push(...studentIds) : activeIds=[...new Set(activeIds.concat(studentIds))] // 多选去重
|
||||||
|
return { def: v, code, isOk, studentIds }
|
||||||
|
})
|
||||||
|
} else if (o.worktype == '填空题') { // 填空题
|
||||||
|
const regex = /<!--BA-->(.*?)<!--EA-->/g // 定义正则表达式,匹配 <!--BA-->xxx<!--EA--> 格式的内容
|
||||||
|
children = (o.title||'').match(regex).map((v,i) => {
|
||||||
|
const def = `填空项 ${i+1}`
|
||||||
|
const code = '( )', txt=v
|
||||||
|
// 改选项的学生id
|
||||||
|
const studentIds = quizFeedList.filter(f => !!(f.feedcontent||'').replace(/#$/,'').split('#')[i]).map(f => f.studentid)||[]
|
||||||
|
activeIds=[...new Set(activeIds.concat(studentIds))] // 多选去重
|
||||||
|
accSum = activeIds.length
|
||||||
|
return { def, code, txt, isOk:true, studentIds }
|
||||||
|
})
|
||||||
|
} else if (o.worktype == '论述题') { // 论述题
|
||||||
|
const code = '( )', def = '论述内容'
|
||||||
|
const studentIds = quizFeedList.filter(f => !!(f.feedcontent||'').replace(/#$/,'')).map(f => f.studentid)||[]
|
||||||
|
activeIds=[...new Set(activeIds.concat(studentIds))] // 多选去重
|
||||||
|
accSum = activeIds.length
|
||||||
|
children = [{ def, code, isOk:true, studentIds }]
|
||||||
|
}
|
||||||
|
const studentSum = studentList.value.length || 0 // 当前推送答题人数
|
||||||
|
const points = percent((activeIds.length / (studentSum||1)).toFixed(2)) // 计算得分率
|
||||||
|
// def: 原始题数据 type 类型 active: 选中 points: 得分率, accSum 题解答人数
|
||||||
|
return { def: o, id: o.id, type: o.worktype, active: [], points, accSum, children }
|
||||||
|
})
|
||||||
|
console.log('获取数据: ', data)
|
||||||
|
dataList.value = data
|
||||||
|
}
|
||||||
|
// 获取-该题各选项-完成进度
|
||||||
|
const ratio_1 = (row, sum = 1) => percent(((row.studentIds.length||0) / (sum||1)).toFixed(2))
|
||||||
|
// 获取-该题-完成进度
|
||||||
|
const ratio_2 = row => percent(((row.accSum||0) / (studentList.value.length||1)).toFixed(2))
|
||||||
|
// 获取-该题进度-txt
|
||||||
|
const getRatioTxt = row => row.type.includes('选题') ? '得分率' : '完成度'
|
||||||
|
// 获取-进度条状态-左侧
|
||||||
|
const getStatus = row => row.isOk ? 'success' : 'exception'
|
||||||
|
// 获取-学生姓名(id)
|
||||||
|
const getStudentName = id => studentList.value.length && (studentList.value.find(o => o.studentid == id)||{})?.name || id
|
||||||
|
// 获取-选中题
|
||||||
|
const getActive = ind => activeTopic.value != ind
|
||||||
|
|
||||||
|
// 答题情况-点击题
|
||||||
|
const clickInfo = async ind => {
|
||||||
|
activeTopic.value = activeTopic.value != ind ? ind : 0
|
||||||
|
setTimeout(() => {scrollToElement('collapse-' + ind)}, 300);
|
||||||
|
// elementPlus.ElMessage.warning('功能未开放!')
|
||||||
|
}
|
||||||
|
// === 通用工具 ===
|
||||||
|
// 滚动到指定位置
|
||||||
|
const scrollToElement = id => {
|
||||||
|
const el = document.getElementById(id)
|
||||||
|
!!el && el.scrollIntoView({ behavior: 'smooth', block: 'center',inline:'center' })
|
||||||
|
}
|
||||||
|
// 百分比现在 0-100
|
||||||
|
const percent = v => v > 1 ? 1 : v < 0 ? 0 : Math.round(v * 100)
|
||||||
|
// Unicode 转 字符 差值65
|
||||||
|
const toCode = (v, b) => b ? v.charCodeAt() - 65 : String.fromCharCode(v + 65)
|
||||||
|
// 判断是否为json字符串
|
||||||
|
const isJson = str => {if(typeof str == 'string'){
|
||||||
|
try {
|
||||||
|
const res = JSON.parse(str)
|
||||||
|
if(typeof res == 'object' && res) return true
|
||||||
|
} catch (error) {}}return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 监听器 ===
|
||||||
|
watchEffect(() => { initData() })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
// 弹窗容器
|
||||||
|
.c-warp{
|
||||||
|
background: #F2F3F5;
|
||||||
|
height: 73vh;
|
||||||
|
margin: 0 !important;
|
||||||
|
.left{padding-left: 0 !important;}
|
||||||
|
.right{padding-right: 0 !important;}
|
||||||
|
.c-item{
|
||||||
|
padding: 10px;
|
||||||
|
background: #fff;
|
||||||
|
border: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 73vh;
|
||||||
|
}
|
||||||
|
.collapse-item{
|
||||||
|
.item-title-o{
|
||||||
|
margin: 0 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
&:last-child{
|
||||||
|
color: red;
|
||||||
|
:deep(.el-collapse-item__header){
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.respond{
|
||||||
|
.c-label{
|
||||||
|
b[t1]{margin-right: 10px;}
|
||||||
|
}
|
||||||
|
.c-childen{
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #CDD0D6;
|
||||||
|
.el-text[t1]{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.t-left{width: 160px;text-align: left;}
|
||||||
|
.c-respond{
|
||||||
|
.el-tag{margin: 0 5px;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right{
|
||||||
|
.title{
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #CDD0D6;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.respond{
|
||||||
|
height: calc(70vh - 65px);
|
||||||
|
overflow: auto;
|
||||||
|
.el-space{padding: 5px;}
|
||||||
|
.card-warp{
|
||||||
|
border: none;
|
||||||
|
:deep(.el-card__body){
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.card-body{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,159 @@
|
||||||
|
<template>
|
||||||
|
<div class="class-reserv-item">
|
||||||
|
<div class="class-reserv-item-body">
|
||||||
|
<div class="class-reserv-item-title1">
|
||||||
|
<el-tag style="margin-left: 5px" :type="item.workclass"> {{ item.worktype }}</el-tag>
|
||||||
|
<label style="margin-left: 10px">{{ item.uniquekey }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="class-reserv-item-title3">
|
||||||
|
<!-- <span v-for="(tag, index) in item.classItemList" :key="index" style="margin-left: 5px">
|
||||||
|
{{ index === 0 ? tag.name : '、' + tag.name }}
|
||||||
|
</span> -->
|
||||||
|
<span>{{ item.classcaption }}</span>
|
||||||
|
| 截止时间:{{ item.deaddate }} | {{ tabactive }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <el-switch v-model="value1" active-text="云同步"> </el-switch> -->
|
||||||
|
<div class="class-reserv-item-tool">
|
||||||
|
<span>
|
||||||
|
<span v-if="item.workdataresultcount!=0" style="color:#000fff; font-weight: 900; font-size: 15px">{{ item.workdataresultcount }}</span>
|
||||||
|
<span v-if="item.workdataresultcount==0">{{ item.workdataresultcount }}</span>
|
||||||
|
/{{ item.workdatacount }}</span>
|
||||||
|
<span>已交</span>
|
||||||
|
</div>
|
||||||
|
<div class="class-reserv-item-tool">
|
||||||
|
<!-- 总人数-已批阅人数 -->
|
||||||
|
<span style="color: #ff7f00; font-weight: 900; font-size: 15px">{{ item.teacherrationgcount?item.workdatacount - item.teacherrationgcount:item.workdatacount }}</span>
|
||||||
|
<span>待批阅</span>
|
||||||
|
</div>
|
||||||
|
<div class="class-reserv-item-tool">
|
||||||
|
<span>
|
||||||
|
<!-- {{ item.averagetime?item.averagetime:0 }} -->
|
||||||
|
<span v-if=" item.averagetime<60 ">
|
||||||
|
<span style="color: #007fff; font-weight: 900; font-size: 15px">{{ item.averagetime }}</span>分钟
|
||||||
|
</span>
|
||||||
|
<span v-if=" item.averagetime==60 ">
|
||||||
|
<span style="color: #007fff; font-weight: 900; font-size: 15px">1</span>小时
|
||||||
|
</span>
|
||||||
|
<span v-if=" item.averagetime>60 ">
|
||||||
|
<span style="color: #007fff; font-weight: 900; font-size: 15px">{{ Math.floor(item.averagetime / 60)}}</span>小时
|
||||||
|
<span style="color: #007fff; font-weight: 900; font-size: 15px">{{ Math.floor(item.averagetime % 60)}}</span>分钟
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span>平均用时</span>
|
||||||
|
</div>
|
||||||
|
<div class="class-reserv-item-tool">
|
||||||
|
<span style="color: #ff6ec7; font-weight: 900; font-size: 15px">{{ item.scoingRate }}</span>
|
||||||
|
<span>得分率</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { useToolState } from '@/store/modules/tool'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
import { createWindow } from '@/utils/tool'
|
||||||
|
import { deleteSmartReserv, startClass, endClass } from '@/api/classManage'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { listEntpcourse } from '@/api/teaching/classwork'
|
||||||
|
const emit = defineEmits(['openEdit', 'deleteReserv'])
|
||||||
|
const props = defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
tabactive: {
|
||||||
|
type: String,
|
||||||
|
default: () => ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
const value1 = ref(true);
|
||||||
|
const basePath = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||||
|
const toolStore = useToolState() // 获取状态管理-tool
|
||||||
|
const openEdit = () => {
|
||||||
|
emit('openEdit', props.item)
|
||||||
|
}
|
||||||
|
const deleteReserv = () => {
|
||||||
|
deleteSmartReserv([props.item.id]).then((res) => {
|
||||||
|
if (res.data === true) {
|
||||||
|
ElMessage({
|
||||||
|
message: '删除成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
emit('deleteReserv', props.item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const startClassR = (item) => {
|
||||||
|
// startClass(item.id).then((res) => {
|
||||||
|
// if (res.data === true) {
|
||||||
|
// item.status = '上课中'
|
||||||
|
// openLesson()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
item.status = '上课中'
|
||||||
|
openLesson()
|
||||||
|
}
|
||||||
|
// const toolStore = useToolState()
|
||||||
|
let wins = null;
|
||||||
|
// 上课-工具类悬浮
|
||||||
|
const openLesson = () => {
|
||||||
|
// startClass(props.item.id)
|
||||||
|
listEntpcourse({
|
||||||
|
evalid: props.item.ex2,
|
||||||
|
edituserid: useUserStore().user.userId,
|
||||||
|
pageSize: 500
|
||||||
|
}).then(async res=>{
|
||||||
|
if (res.rows[0].id) {
|
||||||
|
wins = await createWindow('tool-sphere', { url: '/tool/sphere?entpcourseid=' + res.rows[0].id + "&reservId=" + props.item.id })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const endClassR = (item) => {
|
||||||
|
endClass(item.id).then((res) => {
|
||||||
|
if (res.data === true) {
|
||||||
|
ElMessage({
|
||||||
|
message: '下课成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
item.status = '已结束'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.class-reserv-item {
|
||||||
|
display: flex;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.class-reserv-item-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
.class-reserv-item-title1 {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
label {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.class-reserv-item-tool {
|
||||||
|
margin-left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -119,7 +119,8 @@ const menuList = [{
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '作业批改',
|
name: '作业批改',
|
||||||
icon: 'icon-pigai'
|
icon: 'icon-pigai',
|
||||||
|
path: '/classTask'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '作业统计',
|
name: '作业统计',
|
||||||
|
|
Loading…
Reference in New Issue