diff --git a/package-lock.json b/package-lock.json index 13b2932..09515f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1470,6 +1470,11 @@ } } }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" + }, "@types/webpack": { "version": "4.41.32", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", @@ -11773,6 +11778,22 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vue-uuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vue-uuid/-/vue-uuid-3.0.0.tgz", + "integrity": "sha512-+5DP857xVmTHYd00dMC1c1gVg/nxG6+K4Lepojv9ckHt8w0fDpGc5gQCCttS9D+AkSkTJgb0cekidKjTWu5OQQ==", + "requires": { + "@types/uuid": "^8.3.4", + "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, "vuescroll": { "version": "4.17.3", "resolved": "https://registry.npmjs.org/vuescroll/-/vuescroll-4.17.3.tgz", diff --git a/package.json b/package.json index c8b6e1a..de839a9 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "vue-draggable-resizable": "^2.3.0", "vue-draggable-resizable-gorkys": "^2.4.8", "vue-router": "^3.5.3", + "vue-uuid": "^3.0.0", "vuescroll": "^4.17.3", "vuex": "^3.6.2" }, diff --git a/src/api/index.js b/src/api/index.js index 572e2d8..bc960fc 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -1,26 +1,32 @@ import config from '@/config' export default { - getProjectDetail: 'occupationlab/occupationlab/projectManage/getProjectDetail', - submit: 'python/python/submit', - runPythonCode: 'python/python/runPythonCode', - queryBcJudgmentByBcId: 'judgment/judgment/bcJudgmentPoint/queryBcJudgmentByBcId', - queryTestProject: 'occupationlab/occupationlab/projectManage/getProjectBySystemId', - editExperimentalData: 'occupationlab/occupationlab/experimentalReport/editExperimentalData', - queryBcJudgmentPointByBcId: 'judgment/judgment/bcJudgmentPoint/queryBcJudgmentPointByBcId', - saveCache: 'python/python/saveCache', - getLastCache: 'python/python/getLastCache', - delCache: 'python/python/delCache', - getTheMostRecentlyRunProject: 'python/python/getTheMostRecentlyRunProject', - getDetailById: 'occupationlab/occupationlab/assessment/getDetailById', - pageStuAssessment: 'occupationlab/occupationlab/assessment/pageStuAssessment', - modelClassList: `nakadai/nakadai/model/reference/modelClassList`, - referenceDemoList: `nakadai/nakadai/model/reference/demo/referenceDemoList`, - referenceFindById: `nakadai/nakadai/model/reference/demo/findById`, - checkIsShowBySystemId: `nakadai/nakadai/model/reference/checkIsShowBySystemId`, - fileUpload: `${config.host}nakadai/nakadai/oss/fileUpload`, - importData: `occupationlab/occupationlab/python/file/data/importData`, - lookExcel: `occupationlab/occupationlab/python/file/data/lookExcel`, - lookOver: `occupationlab/occupationlab/python/file/data/lookOver`, - batchDeletion: `occupationlab/occupationlab/python/file/data/batchDeletion`, - myData: `occupationlab/occupationlab/python/file/data/myData` + getProjectDetail: 'occupationlab/occupationlab/projectManage/getProjectDetail', + submit: 'python/python/submit', + runPythonCode: 'python/python/runPythonCode', + queryBcJudgmentByBcId: 'judgment/judgment/bcJudgmentPoint/queryBcJudgmentByBcId', + queryTestProject: 'occupationlab/occupationlab/projectManage/getProjectBySystemId', + editExperimentalData: 'occupationlab/occupationlab/experimentalReport/editExperimentalData', + queryBcJudgmentPointByBcId: 'judgment/judgment/bcJudgmentPoint/queryBcJudgmentPointByBcId', + saveCache: 'python/python/saveCache', + getLastCache: 'python/python/getLastCache', + delCache: 'python/python/delCache', + getTheMostRecentlyRunProject: 'python/python/getTheMostRecentlyRunProject', + getDataProductBoughtByOurSchool: 'data/data/myDate/getDataProductBoughtByOurSchool', + getPurchasedTableByCategory: `data/data/myDate/getPurchasedTableByCategory`, + previewData: `data/data/preview`, + lookupTableFile: `occupationlab/python/table/data/lookupTableFile`, + downloadData:`${config.host}data/data/download`, + fileupload: `${config.host}occupationlab/oss/manage/fileupload`, + getDetailById: 'occupationlab/occupationlab/assessment/getDetailById', + pageStuAssessment: 'occupationlab/occupationlab/assessment/pageStuAssessment', + modelClassList: `nakadai/nakadai/model/reference/modelClassList`, + referenceDemoList: `nakadai/nakadai/model/reference/demo/referenceDemoList`, + referenceFindById: `nakadai/nakadai/model/reference/demo/findById`, + checkIsShowBySystemId: `nakadai/nakadai/model/reference/checkIsShowBySystemId`, + fileUpload: `${config.host}nakadai/nakadai/oss/fileUpload`, + importData: `occupationlab/occupationlab/python/file/data/importData`, + lookExcel: `occupationlab/occupationlab/python/file/data/lookExcel`, + lookOver: `occupationlab/occupationlab/python/file/data/lookOver`, + batchDeletion: `occupationlab/occupationlab/python/file/data/batchDeletion`, + myData: `occupationlab/occupationlab/python/file/data/myData` } \ No newline at end of file diff --git a/src/components/TestPanel.vue b/src/components/TestPanel.vue index 088b22b..fd0ac2e 100644 --- a/src/components/TestPanel.vue +++ b/src/components/TestPanel.vue @@ -4,7 +4,12 @@
-

实训项目

+
+

实训项目

+ + + +
- + @@ -846,4 +851,11 @@ export default { color: #f00; font-size: 20px; } +.info { + color: #bfbfbf; + cursor: pointer; + &:hover { + opacity: .9; + } +} \ No newline at end of file diff --git a/src/config/index.js b/src/config/index.js index 34a7f57..13e6c75 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -10,7 +10,7 @@ let bankPath = `${location.origin}/banksystem` // 银行系统 // 121.37.12.51 | 192.168.31.151 if (isDev) { host = 'http://192.168.31.151:9000/' - host = 'http://121.37.12.51:9000/' + // host = 'http://121.37.12.51:9000/' bankPath = `http://${location.hostname}:8093` } else if (isPro) { host = 'https://occupationlab.com/' diff --git a/src/styles/common.scss b/src/styles/common.scss index 9c72163..9d5f9e0 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -26,6 +26,13 @@ input::-moz-input-placeholder { input::-ms-input-placeholder { color: #333; } +.inline-center { + display: inline-flex; + align-items: center; +} +.el-card { + border: 0 !important; +} body .cus-table.el-table { border-radius: 4px; th{ diff --git a/src/views/Data.vue b/src/views/Data.vue index 376dce4..216886e 100644 --- a/src/views/Data.vue +++ b/src/views/Data.vue @@ -10,7 +10,7 @@
- + @@ -51,9 +51,9 @@ - + -
+
-
-

- 预览 -
- - - - + + + + + + + + + + + + + + + + + + + +
@@ -87,6 +111,12 @@ 确 定 + + + + + +
@@ -95,6 +125,7 @@ import Cookie from 'js-cookie' import util from '@/util' import clipboard from '@/util/clipboard' import breadcrumb from '@/components/breadcrumb' +import axios from 'axios' export default { data() { return { @@ -120,6 +151,15 @@ export default { curId: '', curExpand: '', submited: false, + tables: [], + keywordTable: '', + pageTable: 1, + pageSizeTable: 10, + totalTable: 0, + curTable: '', + previewVisible: false, + previewHead: [], + previewData: [], }; }, components: { @@ -210,13 +250,8 @@ export default { }, // 查看 show(row) { - // 如果是word,pdf,excel,就用预览插件打开,图片等就直接打开 const format = row.fileFormat - // if ('xls,xlsx'.includes(format)) { - // this.$router.push(`/preview?path=${row.filePath}`) - // } else { - window.open((util.isDoc(format) ? 'https://view.officeapps.live.com/op/view.aspx?src=' : '') + row.filePath) - // } + window.open((util.isDoc(format) ? 'https://view.officeapps.live.com/op/view.aspx?src=' : '') + row.filePath) }, // 下载 download(row) { @@ -242,284 +277,144 @@ export default { }, - // 递归查询当前展开的分类下的子分类 - getMoreTable(list,id){ - list.map(n => { - if(n.id == id){ - this.getTable(n,1) - }else if(n.children && n.children.length){ - this.getMoreTable(n.children,id) - } - }) - }, - // 懒加载后新加载出来的数据可能是之前就存在于这个分类下了的,因此是要自动勾选的,所以在这里去拿这个分类下已经存在了的表的id去做一个重新勾选 - renderChecked(){ - let result = [] - let list = this.$refs.typeTree.getCheckedNodes() - list.map((n,i) => { - if(n.tableLen && n.tableLen > n.children.length){ - result = [...result,...n.children.map(n => n.id)] - }else{ - result.push(n.id) - } - }) - this.$refs.typeTree.setCheckedKeys(result) - }, - // 因为数据会有上万条的情况,一次加载出来dom太多了,所以用懒加载去处理 - loadType(e){ - clearTimeout(this.typeTimer) - let typeTree = this.$refs.typeTreeWrap - // 防抖 - this.typeTimer = setTimeout(() => { - this.typeTreeScrollTop = typeTree.scrollTop - this.typeTreeScrollTop - // 如果已经滚动到底部,则加载下面的数据 - if(this.typeTreeScrollTop > 0 && typeTree.scrollHeight - (typeTree.clientHeight + parseInt(typeTree.scrollTop)) < 10){ - this.getMoreTable(this.importTypeList,this.curExpand) - Promise.all(this.tablePromises).then(_ => { - this.renderChecked() - }) - } - },50) - }, - // 查询分类下的表 - getTable(n,isConcat){ - this.tablePromises.push(new Promise((resolve,reject) => { - this.$post(`${this.api.originalListById}?categoryId=${n.realId}&pageNum=${n.typeTreePage ? n.typeTreePage : 1}&pageSize=${this.typeTreeUnit}`).then(res => { - let list = res.list.records - list.map(n => { - n.label = n.showName - n.id = String(n.id) - // 已经存在的表,禁止再次勾选 - if(this.defaultTypeChecked.includes(n.id)){ - n.disabled = true - } - }) - // 如果是懒加载,则拼接 - if(isConcat){ - n.children = n.children.concat(list) - }else{ - n.children = list - n.tableLen = res.list.total - } - // 每次加载完,分页码数自动+1 - n.typeTreePage++ - resolve() - }).catch(res => { - reject() - }) - })) - }, // 导入数据 showData(){ - this.importLoading = true - this.importVisible = true - // 这里查的是原始分类,跟其他任何地方查分类的接口都不一样,不要混淆,如果要改这个地方,要跟后端沟通 - this.$post(this.api.originalList).then(res => { - // 处理id和label。因为这个树形要同时展示分类和表,所以name的key统一用label(分类和表的name的key不一样) - function handleId(data){ - data.map(n => { - n.realId = n.id - // 分类id跟表id可能会冲突,所以加上uuid - n.id = uuid.v4() - n.label = n.categoryName - if(n.children.length){ - handleId(n.children) - } - }) + this.curTable = '' + this.tables = [] + this.importLoading = true + this.importVisible = true + this.$post(this.api.getDataProductBoughtByOurSchool).then(({ data }) => { + this.importTypeList = data + this.importLoading = false + }).catch(res => { + this.importLoading = false + }) + }, + // 查询分类下的表 + getTable(){ + this.$post(`${this.api.getPurchasedTableByCategory}?categoryId=${this.$refs.typeTree.getCurrentKey()}&pageNum=${this.pageTable}&pageSize=${this.pageSizeTable}`).then(({ data }) => { + const list = data.records + list.map(e => { + // 开始结束日期不用显示时间,所以只需要截取日期 + const startTime = e.startTime ? e.startTime.slice(0, 10) : '' + const endTime = e.endTime ? e.endTime.slice(0, 10) : '' + if (startTime && endTime) { + e.timeRange = startTime + ' ~ ' + endTime + } else if (startTime) { + e.timeRange = startTime + } else if (endTime) { + e.timeRange = e.endTime } - handleId(res) - // 查询该分类下已勾选的表。pageSize传3000是防止查的表太少,无法选中分类,因为每个分类初始化的时候只查子下的20个表,后续可根据需求修改 - this.$post(`${this.api.getIdQueryTable}?categoryId=${this.categoryId}&showName=${this.keyword}&pageNum=1&pageSize=3000&updateTime=${this.updateTime ? this.updateTime : ''}`).then(res1 => { - let list = res1.pageList.records - this.defaultTypeChecked = list.map(n => n.copyId) // copyId为原始表id,通过这个id才能选中表 - // 递归处理分页 - const handleId = data => { - data.map(n => { - if(n.children.length){ - handleId(n.children) - }else{ - // 默认分页码数为1 - n.typeTreePage = 1 - this.getTable(n) - } - }) - } - handleId(res) - // 如果是已经勾选了的表,则禁止勾选 - function handleDisabled (list) { - list.map(e => { - if (e.children && e.children.length) { - if (e.children.every(e => e.disabled)) e.disabled = true - handleDisabled(e.children) - } - }) - } - Promise.all(this.tablePromises).then(_ => { - handleDisabled(res) - this.importTypeList = res - this.importLoading = false - }) - }).catch(res => { - this.importLoading = false - }) - }).catch(res => { - this.importLoading = false }) + this.tables = list + this.totalTable = data.total + }).catch(res => {}) }, - // 查询表的字段 - getFields(){ - this.$get(`${this.api.staticPreview}?tableName=${this.tableName}`).then(res => { + handleCurrentChangeTable(val) { + this.pageTable = val + this.getTable() + }, + // 预览 + preview(row){ + this.$get(`${this.api.previewData}?tableName=${row.name}&tableId=${row.id}`).then(res => { let comment = res.comment - let fieldHead = [] - // id和操作时间这两个字段不用展示 + let previewHead = [] + this.curComment = comment // 用来修改表头的,先保存完整的表头,修改表头的时候需要传给后端 comment.map(n => { - n.field != 'id' && n.field != 'operation_time' && fieldHead.push(n) + // 表格上不用展示id和操作时间 + n.field != 'id' && n.field != 'operation_time' && previewHead.push(n) }) - this.fieldHead = fieldHead + this.previewHead = previewHead let data = res.data + // 如果是自定义表头,则在数据最前面加个custom的行,用来修改表头 + isEdit && data.unshift({ + custom: true + }) data.map(n => { for(let i in n){ // 如果是以+0000结尾的,就表明这个是时间,则转化为正常的时间格式 if(typeof n[i] == 'string' && n[i].endsWith('+0000')) n[i] = this.formatDate('yyyy-MM-dd hh:mm:ss',new Date(n[i])) } }) - this.fieldData = data + this.previewData = data + this.previewVisible = true // 非自定义表头进来的,才需要显示预览弹框 }).catch(res => {}) - this.importVisible = true }, - // 分类点击回调 - importTypeClick(data){ - // 如果点击的是表,则加载这个表下面的字段,分类则不用 - if(data.name){ - this.tableName = data.name - this.curId = data.id - this.getFields() - } - }, - // 分类展开的回调 - importTypeExpand(obj,node,com){ - this.curExpand = obj.id - this.renderChecked() - }, - // 关闭导入弹框回调 - closeImport(){ - this.$refs.typeTree.setCheckedKeys([]) - this.categorys = [] - }, - // 查询分类下的表id - getNames(list){ - const categoryId = Number(this.categoryId) - return new Promise((resolve,reject) => { - this.$get(`${this.api.getAllTableInfoByCategoryId}?categoryId=${list.join()}`).then(res => { - const list = res.tableInfo - list.map(e => { - delete e.id // id不用传给后端 - e.categoryId = categoryId - }) - this.categorys = list - resolve() - }).catch(res => {}) - }) - }, - // 保存成功后的操作 - saveSuccess() { + // 上传到python数据列表 + importData(file) { + this.$post(this.api.importData, { + fileFormat: file.fileType || file.fileFormat, + fileName: file.originalFileName || file.fileName, + filePath: file.fileUrl || file.filePath, + fileSize: file.fileSize, + ossFileName: file.ossFileName + }).then(res => { this.$message.success('导入成功') this.getData() this.importVisible = false setTimeout(() => { - this.submited = false - },1000) + this.submited = false + }, 1000) + this.sourceVisible = false + }).catch(res => { + this.submited = false + }) }, // 导入表 confirmImport(){ - if(this.submited) return false // 导入防抖标识 - const checked = this.$refs.typeTree.getCheckedNodes().filter(e => !e.disabled) // 勾选的数据 - // 如果没有选择任何数据 - if(!checked.length) return this.$message.warning('请选择数据') - this.submited = true - // 分类要调另一个接口去查询该分类下的表,所以,要先把分类id和表id分开,再一次性传分类id去查询表,再把查询出来的表跟另外勾选的表拼接,再保存。要做去重 - const typeIds = checked.filter(e => !e.name).map(e => e.realId) // 先筛选出分类id(分类没有name,表才有name),再获取分类真正的id(id作了自增处理,获取的id不是真正的id,realId才是这个分类真正的id) - const tableIds = checked.filter(e => e.name) // 筛选出表 - const categoryId = Number(this.categoryId) // 外面勾选的分类,即要导入数据的分类 - // 给表加categoryId - tableIds.map(e => { - delete e.id - e.categoryId = categoryId - }) - /** - * 根据某个值给数组对象去重 - * @param arr 要处理的数组 - * @param val 去重依据的值 - */ - function uniq (arr, val) { - const res = new Map() - return arr.filter(e => !res.has(e[val]) && res.set(e[val], 1)) - } - // 分类id,如果有分类id,则调接口去查询分类下的表,再新增,否则,直接拿表id去新增 - if (typeIds.length) { - this.getNames(typeIds).then(() => { - const allIds = [...this.categorys, ...tableIds] // 合并通过分类查询到的表id和另外勾选的表id - const data = uniq(allIds, 'name') - const dataList = [] - // 每个接口只新增2000个表,一次新增太多无法请求成功,这里做个分割 - for (let i = 0, len = data.length; i < len; i += 2000) { - dataList.push(data.slice(i, i + 2000)) - } - const promiseList = [] - // 如果切割成2000条一个item,超过6个item的话,就一次调太多次接口了,也有可能会请求失败,所以如果超过6个item,则再分两次去新增,后续如果数据又增加了,可以考虑做个二次分割,即先切割成2000个一个item,如果超过6个item,则再以6个item为单位切割。新增的时候就是每个接口新增2000个表,每次调6次新增,这6次调完后,再调另外的6次,以此循环 - if (dataList.length > 6) { - dataList.slice(0, 6).map(e => { - promiseList.push(new Promise((resolve,reject) => { - this.$post(this.api.saveTable, e).then(res => { - resolve() - }).catch(res => { - reject() - }) - })) - }) - Promise.all(promiseList).then(res => { - dataList.slice(6).map(e => { - promiseList.push(new Promise((resolve,reject) => { - this.$post(this.api.saveTable, e).then(res => { - resolve() - }).catch(res => { - reject() - }) - })) - }) - Promise.all(promiseList).then(res => { - this.saveSuccess() - }).catch(err => { - this.submited = false - }) - }).catch(err => { - this.submited = false - }) - } else { - dataList.map(e => { - promiseList.push(new Promise((resolve,reject) => { - this.$post(this.api.saveTable, e).then(res => { - resolve() - }).catch(res => { - reject() - }) - })) - }) - Promise.all(promiseList).then(res => { - this.saveSuccess() - }).catch(err => { - this.submited = false - }) - } - }) - } else { // 保存表 - this.$post(this.api.saveTable, tableIds).then(res => { - this.saveSuccess() - }).catch(res => { + if(this.submited) return false // 导入防抖标识 + const id = this.curTable // 勾选的数据 + // 如果没有选择任何数据 + if(!id) return this.$message.warning('请选择数据') + const row = this.tables.find(e => e.id === id) + const { name } = row + this.submited = true + // 查询某表的Excel文件是否存在 + this.$post(`${this.api.lookupTableFile}?tableName=${name}`).then(({ data }) => { + // 如果不存在,则下载该表并上传到阿里云 + if (!data) { + // 通过预览接口获取字段 + this.$get(`${this.api.previewData}?tableName=${name}&tableId=${id}`).then(({ comment }) => { + // 下载该表 + axios.get(`${this.api.downloadData}?tableName=${name}&table_id=${id}&fields=${comment.map(e => e.field).join()}&startTime=${row.startTime}&endTime=${row.endTime}&frequency=1`, { + headers: { + token: this.token + }, + responseType: 'blob' + }).then((res) => { + // if (res.data.type != 'multipart/form-data') return util.warningMsg('数据总数为零!') + // 把该表上传到阿里云 + const form = new FormData() + form.append('file', new Blob([res.data], {type: 'application/vnd.ms-excel'})) + form.append('tableName', name) + form.append('token', this.token) + axios({ + method: 'post', + url: `${this.api.fileupload}`, + data: form, + headers: { + token: this.token, + 'Content-Type': 'multipart/form-data' + }, + }).then(({ data }) => { + this.importData(data.filesResult) + }).catch(res => { this.submited = false + }) + }).catch(res => { + this.submited = false }) + }).catch(res => { + this.submited = false + }) + } else { + // 如果数据库里已经有这个表,则直接调导入数据的接口 + this.importData(data) } + }).catch(res => { + this.submited = false + }) } } }; @@ -582,4 +477,9 @@ export default { } } } +/deep/.check-table { + .el-radio__label { + display: none; + } +} \ No newline at end of file diff --git a/src/views/Home.vue b/src/views/Home.vue index 60b7950..ecd4513 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -12,7 +12,12 @@

编程语言

- 我的数据 +
+ + + + 我的数据 +
@@ -61,6 +66,7 @@ export default { return { loaded: false, // 页面是否加载完的标识,页面默认隐藏,一进来先显示加载条,接口加载完后再显示页面,不然一开始会一闪而过没有样式的页面 loadIns: null, // loading实例 + fromManager: Cookie.get('admin-fromManager'), // 是否是从教师端进入 courseId: Cookie.get('admin-courseId'), // 课程id curriculumName: Cookie.get('admin-curriculumName') ? unescape(Cookie.get('admin-curriculumName')) : 'python', // 课程名称 assessmentId: Cookie.get('admin-assessmentId'), // 考核id @@ -165,7 +171,11 @@ export default { back() { let href = this.$config.isDev ? `http://${location.hostname}:8082/#/` : - `${location.origin}${this.$config.isPro ? '' : '/student'}/#/` + `${location.origin}${this.fromManager ? + '/admin' : + this.$config.isPro ? + '' : + '/student'}/#/` // 考核 if (this.assessmentId) { href += `ass/list` @@ -269,4 +279,11 @@ export default { border-bottom: none; } } +.info { + color: #bfbfbf; + cursor: pointer; + &:hover { + opacity: .9; + } +} \ No newline at end of file