diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 55db58d..517a19c 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -10,6 +10,7 @@ module.exports = {
],
rules: {
'vue/require-default-prop': 'off',
- 'vue/multi-word-component-names': 'off'
+ 'vue/multi-word-component-names': 'off',
+ 'prettier/prettier': 'off'
}
}
diff --git a/electron.vite.config.mjs b/electron.vite.config.mjs
index c48cc00..d5d6421 100644
--- a/electron.vite.config.mjs
+++ b/electron.vite.config.mjs
@@ -25,6 +25,7 @@ export default defineConfig({
proxy: {
'/dev-api': {
target: 'http://27.128.240.72:7865',
+ // target: 'http://36.134.181.164:7863',
// target: 'http://192.168.2.52:7863',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
diff --git a/package.json b/package.json
index d5b805b..0774367 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,9 @@
"@electron/remote": "^2.1.2",
"@element-plus/icons-vue": "^2.3.1",
"@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",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
diff --git a/src/renderer/src/api/classTask/index.js b/src/renderer/src/api/classTask/index.js
new file mode 100644
index 0000000..cd13685
--- /dev/null
+++ b/src/renderer/src/api/classTask/index.js
@@ -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
+ })
+}
diff --git a/src/renderer/src/components/refile-preview/index.vue b/src/renderer/src/components/refile-preview/index.vue
new file mode 100644
index 0000000..227a465
--- /dev/null
+++ b/src/renderer/src/components/refile-preview/index.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
diff --git a/src/renderer/src/components/refile-preview/types.ts b/src/renderer/src/components/refile-preview/types.ts
new file mode 100644
index 0000000..167bbff
--- /dev/null
+++ b/src/renderer/src/components/refile-preview/types.ts
@@ -0,0 +1,7 @@
+export interface FileProps {
+ id?: number;
+ type?: string;
+ fileType?: string;
+ raw?: File;
+ filePath?: string;
+}
diff --git a/src/renderer/src/components/refile-preview/useReadFile.ts b/src/renderer/src/components/refile-preview/useReadFile.ts
new file mode 100644
index 0000000..d93e785
--- /dev/null
+++ b/src/renderer/src/components/refile-preview/useReadFile.ts
@@ -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
+ };
+}
+
diff --git a/src/renderer/src/hooks/useProcessList.js b/src/renderer/src/hooks/useProcessList.js
new file mode 100644
index 0000000..d53f73e
--- /dev/null
+++ b/src/renderer/src/hooks/useProcessList.js
@@ -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 += `
`
+ }
+ const char = String.fromCharCode(65 + j)
+ tmp += `
${char}.${workDescArr[j]}
`
+ if (j % 2 == 1) {
+ tmp += '
'
+ }
+ }
+ // j此刻已自增1, 故当选项为单数时, 需要补充结束标签
+ if (j % 2 == 1) {
+ tmp += ''
+ }
+
+ // 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('
')
+
+ 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 = `${index + 1}. ${item.title}
`
+ let tmp = ''
+ let j = 0
+ let optionsArr = item.options
+ for (; j < optionsArr.length; j++) {
+ if (j % 2 == 0) {
+ tmp += ``
+ }
+ const char = String.fromCharCode(65 + j)
+ tmp += `
${char}.${optionsArr[j]}
`
+ if (j % 2 == 1) {
+ tmp += '
'
+ }
+ }
+ // j此刻已自增1, 故当选项为单数时, 需要补充结束标签
+ if (j % 2 == 1) {
+ tmp += ''
+ }
+
+ workDescHtml += tmp
+ } else if (item.type == '填空题' || item.type == '判断题' || item.type == '主观题') {
+ workDescHtml += `${index + 1}. ${item.title}
`
+ }
+ })
+ workDescHtml += ''
+ 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 += `${index + 1}. ${answer}
`
+ } else if (item.type == '填空题') {
+ const answer = answerArr.join('、')
+ workAnswerHtml += `${index + 1}. ${answer}
`
+ } else if (item.type == '判断题') {
+ const answer = answerArr
+ .map((item) => {
+ return item === '1' ? '正确' : '错误'
+ })
+ .join('、')
+ workAnswerHtml += `${index + 1}. ${answer}
`
+ } else if (item.type == '主观题') {
+ // 复合题里面的主观题只有一个答案,或没填
+ const answer = answerArr.join('、')
+ if (answerArr[0]) {
+ workAnswerHtml += `${index + 1}. ${answer}
`
+ } else {
+ workAnswerHtml += `${index + 1}. ${answer}答案不唯一,请参考分析解答点评!
`
+ }
+ }
+ })
+ 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 += ``
+ }
+ const char = String.fromCharCode(65 + j)
+ tmp += `
${char}.${workDescArr[j]}
`
+ if (j % 2 == 1) {
+ tmp += '
'
+ }
+ }
+ if (j % 2 == 0) {
+ tmp += ''
+ }
+ 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: "①②#&①③#&②④#&③④" || "为了活着
#&为了填报肚子
#&为了吃饭而吃饭
"
+ 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]*>/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`;
+ }
+ const char = String.fromCharCode(65+j);
+ tmp += `${char}.${jsonArr[j]}
`;
+ if(j%2 == 1){
+ tmp += '';
+ }
+ }
+
+ if(j%2== 0){
+ tmp += '';
+ }
+ 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';
+ }
+ 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 {
+ 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('
');
+
+ 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 += ''+(index+1)+'.'+item.replace(/#&/g, ',')+'
';
+ })
+ row[i].workanswerFormat = tmp;
+
+ }
+ else {
+ //处理答案
+ row[i].workanswerFormat = '见试题解答内容';
+ }
+ */
+ }
+ }
+}
diff --git a/src/renderer/src/router/index.js b/src/renderer/src/router/index.js
index d9522e3..4a8a2f4 100644
--- a/src/renderer/src/router/index.js
+++ b/src/renderer/src/router/index.js
@@ -1,3 +1,8 @@
+/*
+ * @Author: 苦逼程序猿
+ * @Date: 2024-09-06 16:15:32
+ * @Warning: 千行代码,Bug露锋芒。
+ */
import { createRouter, createWebHashHistory } from 'vue-router'
import Layout from '../layout/index.vue'
@@ -63,6 +68,12 @@ export const constantRoutes = [
name: 'class',
meta: {title: '班级中心'},
},
+ {
+ path: '/classTask',
+ component: () => import('@/views/classTask/classTask.vue'),
+ name: 'class',
+ meta: {title: '作业批改'},
+ },
{
path: '/examReport',
component: () => import('@/views/examReport/index.vue'),
diff --git a/src/renderer/src/store/modules/overview.js b/src/renderer/src/store/modules/overview.js
new file mode 100644
index 0000000..46f58f6
--- /dev/null
+++ b/src/renderer/src/store/modules/overview.js
@@ -0,0 +1,18 @@
+import { defineStore } from 'pinia'
+const overviewStore = defineStore(
+ 'overview',
+ {
+ state: () => {
+ return {
+ tableList:[]
+ }
+ },
+ actions: {
+ getTableList(data){
+ this.tableList = [...data]
+ }
+ }
+ })
+export default overviewStore
+
+
diff --git a/src/renderer/src/utils/date.js b/src/renderer/src/utils/date.js
index f70414b..2a178ca 100644
--- a/src/renderer/src/utils/date.js
+++ b/src/renderer/src/utils/date.js
@@ -108,4 +108,29 @@ export const getAfterMinutes = (m) => {
let minutes = afterMinutes.getMinutes();
minutes = minutes < 10 ? ('0' + minutes) : 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
}
\ No newline at end of file
diff --git a/src/renderer/src/views/classTask/classTask.vue b/src/renderer/src/views/classTask/classTask.vue
new file mode 100644
index 0000000..bfacb90
--- /dev/null
+++ b/src/renderer/src/views/classTask/classTask.vue
@@ -0,0 +1,413 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/src/views/classTask/container/classOverview.vue b/src/renderer/src/views/classTask/container/classOverview.vue
new file mode 100644
index 0000000..6b6687a
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/classOverview.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+ 学情分布
+
+
+
+
+
+
+
+
+
+
+ 时长分析
+
+
+
+
+
+
+
+
+
+
+ 知识点概览
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/src/views/classTask/container/classOverview/distribution.vue b/src/renderer/src/views/classTask/container/classOverview/distribution.vue
new file mode 100644
index 0000000..5ff15c1
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/classOverview/distribution.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/renderer/src/views/classTask/container/classOverview/distribution/echarts.vue b/src/renderer/src/views/classTask/container/classOverview/distribution/echarts.vue
new file mode 100644
index 0000000..9f6ae08
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/classOverview/distribution/echarts.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
diff --git a/src/renderer/src/views/classTask/container/classOverview/distribution/stuList.vue b/src/renderer/src/views/classTask/container/classOverview/distribution/stuList.vue
new file mode 100644
index 0000000..df21f66
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/classOverview/distribution/stuList.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+ {{stuItem.studentname}}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/renderer/src/views/classTask/container/classOverview/knowledge.vue b/src/renderer/src/views/classTask/container/classOverview/knowledge.vue
new file mode 100644
index 0000000..851f587
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/classOverview/knowledge.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+ {{scope.row.scoingRate + '%'}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/renderer/src/views/classTask/container/classOverview/timeAnalyse.vue b/src/renderer/src/views/classTask/container/classOverview/timeAnalyse.vue
new file mode 100644
index 0000000..c65e5ae
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/classOverview/timeAnalyse.vue
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
diff --git a/src/renderer/src/views/classTask/container/item-dialog-score.vue b/src/renderer/src/views/classTask/container/item-dialog-score.vue
new file mode 100644
index 0000000..105574a
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/item-dialog-score.vue
@@ -0,0 +1,976 @@
+
+
+
+
+
+ {{ classWorkFormScore.name }} 答题详情
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ quItem.worktag }}
+
+
+
+
+
+
+
+
+ 【答案】
+
+ 【分析】
+
+ 【解答】
+
+ 【点评】
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 参考答案:
+
+ {{ quItem.workanswerFormat.replace(/<[^>]+>/g, '') }}
+
+
+
+
+
+ 学生答案:
+
+
+
+ {{ stuItem.feedcontent.replace('#', '、') }}
+
+
+
+
+
+ {{
+ JSON.parse(quItem.workdesc)
+ .map((item, index) => {
+ if (item == stuItem.feedcontent) {
+ return String.fromCharCode(65 + Number(index))
+ }
+ })
+ .filter(Boolean)[0]
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 分
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 温馨提示:点击图片可放大预览
+
+
+
+
+
+
+
+
+ 温馨提示:点击此处 可预览其他类型附件!
+
+
+
+
+
+
+
+
+
+
+
+
学生答题附件内容
+
+
+
+ 温馨提示:点击图片可放大预览
+
+
+
+
+
+
+
+
+ 温馨提示:点击此处 可预览其他类型附件!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+ 预览
+
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+ 预览
+
+
+
+
+
+
+ 预览展示区域
+ 温馨提示:若预览失败,{{ props.name }}可点击此处下载!
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/src/views/classTask/container/item-dialog.vue b/src/renderer/src/views/classTask/container/item-dialog.vue
new file mode 100644
index 0000000..1eba989
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/item-dialog.vue
@@ -0,0 +1,754 @@
+
+
+
+
+
+ {{ classWorkAnalysis.title }}答题情况
+ {{
+ classWorkAnalysis.worktype
+ }}
+
+
+
+
+ 作业批阅
+ 作业概况
+ 作业报告
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 待批阅
+
+ 优
+ 优-
+ 良
+ 良-
+ 差
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/src/views/classTask/container/quizStats.vue b/src/renderer/src/views/classTask/container/quizStats.vue
new file mode 100644
index 0000000..d04f275
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/quizStats.vue
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+ {{item.def?.titletext}}
+
+ {{index+1}}
+
+
+ {{item.type}} {{getRatioTxt(item)}}
+ {{item.points}}%
+
+
+
+ 作答情况
+ (已经完成 {{item.accSum}} 人)
+
+
+
+
+
+
+
+
+
+
+ {{it.studentIds.length}} 人/占 {{ratio_1(it, item.accSum)}}%
+
+
+
+
+
+
+
+ {{getStudentName(sid)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
答题情况
+
+
+
+
+
+
+
+ {{index+1}}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/renderer/src/views/classTask/container/task-item.vue b/src/renderer/src/views/classTask/container/task-item.vue
new file mode 100644
index 0000000..ae63cc1
--- /dev/null
+++ b/src/renderer/src/views/classTask/container/task-item.vue
@@ -0,0 +1,159 @@
+
+
+
+
+ {{ item.worktype }}
+
+
+
+
+
+ {{ item.classcaption }}
+ | 截止时间:{{ item.deaddate }} | {{ tabactive }}
+
+
+
+
+
+ {{ item.workdataresultcount }}
+ {{ item.workdataresultcount }}
+ /{{ item.workdatacount }}
+ 已交
+
+
+
+ {{ item.teacherrationgcount?item.workdatacount - item.teacherrationgcount:item.workdatacount }}
+ 待批阅
+
+
+
+
+
+ {{ item.averagetime }}分钟
+
+
+ 1小时
+
+
+ {{ Math.floor(item.averagetime / 60)}}小时
+ {{ Math.floor(item.averagetime % 60)}}分钟
+
+
+ 平均用时
+
+
+ {{ item.scoingRate }}
+ 得分率
+
+
+
+
+
diff --git a/src/renderer/src/views/desktop/index.vue b/src/renderer/src/views/desktop/index.vue
index 8268cdb..566d764 100644
--- a/src/renderer/src/views/desktop/index.vue
+++ b/src/renderer/src/views/desktop/index.vue
@@ -119,7 +119,8 @@ const menuList = [{
},
{
name: '作业批改',
- icon: 'icon-pigai'
+ icon: 'icon-pigai',
+ path: '/classTask'
},
{
name: '作业统计',