diff --git a/package-lock.json b/package-lock.json
index 9a505de..13b2932 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5422,6 +5422,11 @@
"schema-utils": "^2.5.0"
}
},
+ "file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+ },
"filesize": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
diff --git a/package.json b/package.json
index 6db4a8e..c8b6e1a 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"clipboard": "^2.0.10",
"core-js": "^3.19.3",
"element-ui": "^2.15.6",
+ "file-saver": "^2.0.5",
"jquery": "^3.6.0",
"js-cookie": "^3.0.1",
"lib-flexible": "^0.3.2",
diff --git a/src/assets/images/dataforward.png b/src/assets/images/dataforward.png
new file mode 100644
index 0000000..6c75327
Binary files /dev/null and b/src/assets/images/dataforward.png differ
diff --git a/src/components/TestPanel.vue b/src/components/TestPanel.vue
index 721d413..088b22b 100644
--- a/src/components/TestPanel.vue
+++ b/src/components/TestPanel.vue
@@ -463,7 +463,20 @@ export default {
type: 'warning',
center: true
}).then(() => {
- this.submit()
+ // 如果全部都没运行直接点提交,则主动运行一个空代码。(不然会造成上次运行的结果,这次进来不运行直接提交的话,会无法取到运行结果)
+ if (!pointList.find(e => e.codeId)) {
+ this.$post(this.api.runPythonCode, {
+ code: '',
+ bcId: pointList[0].judgmentId,
+ cid: this.courseId,
+ projectId: this.projectId
+ }).then(({ codeId }) => {
+ this.$parent.workbench[0].codeId = codeId
+ this.submit()
+ }).catch(err => {})
+ } else {
+ this.submit()
+ }
}).catch(() => {})
},
// 提交
diff --git a/src/components/codemirror.vue b/src/components/codemirror.vue
index 71d282b..fef0e0c 100644
--- a/src/components/codemirror.vue
+++ b/src/components/codemirror.vue
@@ -43,6 +43,7 @@
运行成功
+
导出结果
@@ -479,6 +480,11 @@ export default {
downloadPic(i) {
this.$refs['picLink' + i][0].click()
},
+ exportResult() {
+ var FileSaver = require('file-saver');
+ var blob = new Blob([this.runResult], {type: "text/plain;charset=utf-8"});
+ FileSaver.saveAs(blob, 'result.csv')
+ },
// 获取正式答案
getTips() {
this.tipsVisible = true
diff --git a/src/views/Data.vue b/src/views/Data.vue
index 025ba89..376dce4 100644
--- a/src/views/Data.vue
+++ b/src/views/Data.vue
@@ -5,9 +5,7 @@
@@ -40,6 +38,55 @@
+
+
+
+ -
+
+
+
+ - 本地上传
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -58,7 +105,21 @@ export default {
keyword: '',
page: 1,
pageSize: 10,
- total: 0
+ total: 0,
+ sourceVisible: false,
+
+ importVisible: false,
+ defaultTypeActive: [],
+ defaultTypeChecked: [],
+ importLoading: false,
+ tablePromises: [],
+ importTypeList: [],
+ fieldData: [],
+ fieldHead: [],
+ tableName: '',
+ curId: '',
+ curExpand: '',
+ submited: false,
};
},
components: {
@@ -119,6 +180,7 @@ export default {
ossFileName: file.ossFileName
}).then(res => {
this.$message.success('导入成功')
+ this.sourceVisible = false
this.getData()
}).catch(res => {})
},
@@ -177,6 +239,287 @@ export default {
},
handleSelectionChange(val) {
this.multipleSelection = val
+ },
+
+
+ // 递归查询当前展开的分类下的子分类
+ 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)
+ }
+ })
+ }
+ 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
+ })
+ },
+ // 查询表的字段
+ getFields(){
+ this.$get(`${this.api.staticPreview}?tableName=${this.tableName}`).then(res => {
+ let comment = res.comment
+ let fieldHead = []
+ // id和操作时间这两个字段不用展示
+ comment.map(n => {
+ n.field != 'id' && n.field != 'operation_time' && fieldHead.push(n)
+ })
+ this.fieldHead = fieldHead
+
+ let data = res.data
+ 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
+ }).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() {
+ this.$message.success('导入成功')
+ this.getData()
+ this.importVisible = false
+ setTimeout(() => {
+ this.submited = false
+ },1000)
+ },
+ // 导入表
+ 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 => {
+ this.submited = false
+ })
+ }
}
}
};
@@ -211,4 +554,32 @@ export default {
cursor: pointer;
}
}
+/deep/.source-list {
+ display: flex;
+ justify-content: space-between;
+ li {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ height: 100px;
+ border: 1px solid #ccc;
+ border-radius: 8px;
+ cursor: pointer;
+ img {
+ width: 90%;
+ }
+ &:hover {
+ border-color: #007eff;
+ }
+ }
+ .upload {
+ width: 48%;
+ .el-upload {
+ width: 100%;
+ &:focus {
+ color: #007eff;
+ }
+ }
+ }
+}
\ No newline at end of file