Merge branch 'master' of https://e.coding.net/ctcdevteam/cma_search
This commit is contained in:
commit
acb25ae76e
File diff suppressed because one or more lines are too long
|
@ -27,10 +27,12 @@
|
|||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"pdfobject": "^2.3.0",
|
||||
"sortablejs": "^1.15.2",
|
||||
"vod-js-sdk-v6": "^1.4.12",
|
||||
"vue": "2.6.10",
|
||||
"vue": "2.7",
|
||||
"vue-pdf": "^4.2.0",
|
||||
"vue-router": "3.0.6",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "3.1.0",
|
||||
"xlsx": "^0.15.5"
|
||||
},
|
||||
|
|
|
@ -27,10 +27,11 @@ export function updateQuestioncat(id, data) {
|
|||
//删除题目类型
|
||||
export function deleteQuestioncat(id) {
|
||||
return request({
|
||||
url: `/exam/questioncat/${id}/`,
|
||||
url: `/exam/question/${id}/`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
//题目列表
|
||||
export function getQuestionList(query) {
|
||||
return request({
|
||||
|
@ -69,6 +70,14 @@ export function deleteQuestion(id) {
|
|||
method: 'delete'
|
||||
})
|
||||
}
|
||||
//批量删除题目类型
|
||||
export function deletesQuestion(data) {
|
||||
return request({
|
||||
url: `/exam/question/deletes/`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
//导入题目
|
||||
export function importQuestion(data) {
|
||||
return request({
|
||||
|
@ -82,7 +91,7 @@ export function exportQuestion(data) {
|
|||
return request({
|
||||
url: `/exam/question/export/`,
|
||||
method: 'get',
|
||||
params: query
|
||||
params: data
|
||||
})
|
||||
}
|
||||
//启用题目
|
||||
|
@ -213,6 +222,15 @@ export function updateExamRecord(id, data) {
|
|||
data
|
||||
})
|
||||
}
|
||||
|
||||
//导出考试记录
|
||||
export function exportRecord(data) {
|
||||
return request({
|
||||
url: `/exam/examrecord/export/`,
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
//删除考试记录
|
||||
export function deleteExamRecord(id) {
|
||||
return request({
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg t="1717725053175" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3023" width="200" height="200"><path d="M153.6 58.88v901.632h706.56V58.88H153.6z m641.024 834.56H218.624v-768h576v768z" fill="#ffffff" p-id="3024"></path><path d="M399.36 228.352h-35.84c-2.048 0-4.608 1.536-5.12 3.584l-31.232 186.368c-0.512 4.096 2.56 7.68 6.144 7.68h32.768c2.048 0 4.096-1.536 4.096-3.584l33.28-188.416c1.024-3.072-1.024-5.632-4.096-5.632zM537.088 242.176c-8.704-10.24-23.552-15.36-39.424-15.36-15.872 0-32.256 5.12-44.544 15.36-12.288 9.728-19.968 22.016-23.552 42.496l-15.36 86.016c-3.584 20.48 0 32.256 8.704 42.496 8.704 10.24 23.552 15.36 39.424 15.36 15.872 0 32.256-5.12 44.544-15.36 12.288-9.728 19.968-22.016 23.552-42.496l15.36-86.016c3.584-20.48 0-32.768-8.704-42.496z m-35.328 40.96l-15.872 89.6c-2.048 10.752-8.704 16.384-16.896 16.384s-12.8-5.12-11.264-16.384l15.872-89.6c2.048-10.752 8.704-16.384 16.896-16.384 8.192 0 13.312 5.12 11.264 16.384zM676.352 242.176c-8.704-10.24-23.552-15.36-39.424-15.36s-32.256 5.12-44.544 15.36c-12.288 9.728-19.968 22.016-23.552 42.496l-15.36 86.016c-3.584 20.48 0 32.256 8.704 42.496 8.704 10.24 23.552 15.36 39.424 15.36 15.872 0 32.256-5.12 44.544-15.36 12.288-9.728 19.968-22.016 23.552-42.496l15.36-86.016c3.584-20.48 0-32.768-8.704-42.496z m-35.328 40.96l-15.872 89.6c-2.048 10.752-8.704 16.384-16.896 16.384s-12.8-5.12-11.264-16.384l15.872-89.6c2.048-10.752 8.704-16.384 16.896-16.384 8.192 0 13.312 5.12 11.264 16.384zM313.856 515.584h347.648V573.44H313.856zM313.856 638.976h275.968v57.856H313.856zM313.856 767.488h275.968v57.856H313.856z" fill="#ffffff" p-id="3025"></path></svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -55,8 +55,11 @@
|
|||
@sort-change="changeSort"
|
||||
>
|
||||
<el-table-column type="index" width="50"></el-table-column>
|
||||
<el-table-column align="left" label="类型">
|
||||
<!-- <el-table-column align="left" label="类型">
|
||||
<template slot-scope="scope">{{ scope.row.type }}</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column align="left" label="考试名称">
|
||||
<template slot-scope="scope">{{ scope.row.exam_name }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="用户">
|
||||
<template slot-scope="scope">{{ scope.row.create_by_name}}</template>
|
||||
|
@ -82,20 +85,32 @@
|
|||
<el-table-column align="left" label="答题时间">
|
||||
<template slot-scope="scope">{{ scope.row.start_time }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="证书路径" prop="cert_path">
|
||||
<template slot-scope="scope">
|
||||
<img v-if="scope.row.cert_path" :src="scope.row.cert_path" @click="handlePictureCardPreview(scope.row.cert_path)" alt="图片" height="30" width="50"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="单位名称">
|
||||
<template slot-scope="scope">{{ scope.row.blong_dept_name }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="考试时间">
|
||||
<template slot-scope="scope">{{ scope.row.create_time }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="操作" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
v-if="scope.row.type=='正式考试'"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleExport(scope)"
|
||||
>生成Word</el-button>
|
||||
<el-button
|
||||
>生成Word</el-button> -->
|
||||
<!-- <el-button
|
||||
v-if="scope.row.type=='正式考试'"
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleExport2(scope)"
|
||||
>重新生成</el-button>
|
||||
>重新生成</el-button> -->
|
||||
<el-button
|
||||
v-if="scope.row.type=='正式考试'"
|
||||
type="danger"
|
||||
|
@ -116,14 +131,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { getExamRecordList, exportTest, exportwTest, deleteExamRecord,issue } from "@/api/exam";
|
||||
import { getExamRecordList, exportRecord, exportwTest, deleteExamRecord,issue } from "@/api/exam";
|
||||
import checkPermission from "@/utils/permission";
|
||||
import Pagination from "@/components/Pagination";
|
||||
|
||||
const listQuery = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
type:'正式考试',
|
||||
search:''
|
||||
};
|
||||
export default {
|
||||
|
@ -193,13 +207,13 @@
|
|||
methods: {
|
||||
checkPermission,
|
||||
getQuery() {
|
||||
if(this.$route.params.exam){
|
||||
this.listQuery.exam = this.$route.params.exam;
|
||||
this.getList()
|
||||
}else{
|
||||
this.getList()
|
||||
if(this.$route.query.exam){
|
||||
this.listQuery.exam = this.$route.query.exam;
|
||||
}
|
||||
|
||||
this.getList()
|
||||
},
|
||||
handlePictureCardPreview(url){
|
||||
window.open(url);
|
||||
},
|
||||
getList() {
|
||||
this.listLoading = true;
|
||||
|
@ -216,11 +230,10 @@
|
|||
this.listQuery = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
type:'正式考试',
|
||||
search:'',
|
||||
};
|
||||
this.value = []
|
||||
this.getList();
|
||||
// this.getList();
|
||||
},
|
||||
handleExport(scope) {
|
||||
const loading = this.$loading({text: '正在生成word...',});
|
||||
|
@ -236,7 +249,7 @@
|
|||
type: "error"
|
||||
})
|
||||
.then(async () => {
|
||||
await deleteExamtest(scope.row.id);
|
||||
await deleteExamRecord(scope.row.id);
|
||||
this.getList()
|
||||
this.$message({
|
||||
type: "success",
|
||||
|
@ -256,10 +269,10 @@
|
|||
},
|
||||
exportTest() {
|
||||
const loading = this.$loading();
|
||||
exportTest(this.listQuery).then(response => {
|
||||
loading.close()
|
||||
window.open(response.data.path, "_blank");
|
||||
});
|
||||
exportRecord(this.listQuery).then(res=>{
|
||||
loading.close()
|
||||
window.open(res.data.path, "_blank");
|
||||
}).catch(e=>{loading.close()})
|
||||
},
|
||||
setTimeRange(){
|
||||
this.listQuery.start = this.value[0],
|
||||
|
|
|
@ -39,9 +39,9 @@
|
|||
<el-table-column label="考试地点">
|
||||
<template slot-scope="scope">{{ scope.row.place }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="参考机会">
|
||||
<!-- <el-table-column label="参考机会">
|
||||
<template slot-scope="scope">{{ scope.row.chance }}</template>
|
||||
</el-table-column>
|
||||
</el-table-column> -->
|
||||
<el-table-column label="开启时间">
|
||||
<template slot-scope="scope">{{ scope.row.open_time }}</template>
|
||||
</el-table-column>
|
||||
|
@ -96,9 +96,9 @@
|
|||
<el-form-item label="考试地点" prop="place">
|
||||
<el-input v-model="exam.place" placeholder="考试地点" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参考机会" prop="chance">
|
||||
<!-- <el-form-item label="参考机会" prop="chance">
|
||||
<el-input-number v-model="exam.chance" placeholder="参考机会" :min="1"/>
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="开启时间" prop="open_time">
|
||||
<el-date-picker
|
||||
v-model="exam.open_time"
|
||||
|
@ -154,17 +154,27 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="考试部门" prop="participant_dep" style="width:100%" clearable collapse-tags filterable >
|
||||
<el-button @click="selectAll">全选部门</el-button>
|
||||
<el-select v-model="exam.participant_dep" multiple collapse-tags filterable placeholder = "选择部门" >
|
||||
<!-- <el-button @click="selectAll">全选部门</el-button> -->
|
||||
<!-- <el-select v-model="exam.participant_dep" multiple collapse-tags filterable placeholder = "选择部门" >
|
||||
<el-option
|
||||
v-for="item in depOptions"
|
||||
:key = "item.id"
|
||||
:label = "item.name"
|
||||
:value = "item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-select> -->
|
||||
<div>
|
||||
<el-transfer
|
||||
:props="{key: 'id',label: 'name'}"
|
||||
:data="depOptions"
|
||||
v-model="exam.participant_dep"
|
||||
:titles="['未选部门','指定考试部门']"
|
||||
@change="handleChange"
|
||||
@on-change="handleOnChange"
|
||||
></el-transfer>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<!-- <el-form-item label="考试人员" prop="participant_user" style="width:100%" clearable>
|
||||
<el-select v-model="exam.participant_user" multiple filterable placeholder = "请选择人员">
|
||||
<el-option
|
||||
|
@ -184,6 +194,7 @@
|
|||
:total="total"
|
||||
></el-pagination>
|
||||
</el-form-item> -->
|
||||
|
||||
</el-form>
|
||||
<div style="text-align:right;">
|
||||
<el-button type="danger" @click="dialogVisible=false">取消</el-button>
|
||||
|
@ -197,7 +208,11 @@
|
|||
import {getUserList} from "@/api/user";
|
||||
import checkPermission from "@/utils/permission";
|
||||
import Pagination from "@/components/Pagination"
|
||||
import Vue from 'vue'
|
||||
// import ElementUI from 'element-ui'
|
||||
// import 'element-ui/lib/theme-chalk/index.css'
|
||||
|
||||
// Vue.use(ElementUI)
|
||||
|
||||
const defaultexam = {
|
||||
id: "",
|
||||
|
@ -207,7 +222,7 @@
|
|||
close_time: null,
|
||||
proctor_name:'',
|
||||
proctor_phone:'',
|
||||
chance:3,
|
||||
chance:1,
|
||||
paper:'',
|
||||
certificate:'',
|
||||
course_name:[],
|
||||
|
@ -270,9 +285,9 @@
|
|||
this.paperOptions = res.data
|
||||
})
|
||||
},
|
||||
selectAll(){
|
||||
this.exam.participant_dep = this.depOptions.map(option => option.id);
|
||||
},
|
||||
// selectAll(){
|
||||
// this.exam.participant_dep = this.depOptions.map(option => option.id);
|
||||
// },
|
||||
getList() {
|
||||
this.listLoading = true;
|
||||
debugger;
|
||||
|
@ -281,6 +296,12 @@
|
|||
this.listLoading = false;
|
||||
});
|
||||
},
|
||||
handleChange(value, direction, movedKeys) {
|
||||
console.log(value, direction, movedKeys)
|
||||
},
|
||||
handleOnChange(value, direction, movedKeys) {
|
||||
console.log(value, direction, movedKeys)
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
// 改变每页显示的条数
|
||||
this.pageSize = val;
|
||||
|
@ -353,7 +374,7 @@
|
|||
});
|
||||
})
|
||||
.catch(err => {
|
||||
// console.error(err);
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
handleView(scope){
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
|
||||
<el-button slot="reference">Excel导入</el-button>
|
||||
</el-popover>
|
||||
<el-button type="primary" icon="el-icon-download" @click="exportQuestion">导出Excel</el-button>
|
||||
<el-button type="primary" icon="el-icon-download" @click="exportQuestions">导出Excel</el-button>
|
||||
<el-button type="danger" v-if="checkPermission(['question_batch_delete'])" :disabled="this.selects.length==0" @click="batchDelete()">批量删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="tableData" style="width: 100%;margin-top:10px;" border stripe fit v-loading="listLoading"
|
||||
|
@ -104,6 +105,7 @@
|
|||
importQuestion,
|
||||
exportQuestion,
|
||||
enableQuestions,
|
||||
deletesQuestion,
|
||||
} from "@/api/exam";
|
||||
import checkPermission from "@/utils/permission";
|
||||
import Pagination from "@/components/Pagination";
|
||||
|
@ -159,7 +161,6 @@ export default {
|
|||
handleUploadSuccess(res, file) {
|
||||
if (res.code == 201) {
|
||||
const loading = this.$loading({ text: "正在导入中..." })
|
||||
console.log(res.data);
|
||||
importQuestion(res.data).then(response => {
|
||||
loading.close()
|
||||
if (response.code == 200) {
|
||||
|
@ -235,7 +236,7 @@ export default {
|
|||
// console.error(err);
|
||||
});
|
||||
},
|
||||
exportQuestion() {
|
||||
exportQuestions() {
|
||||
const loading = this.$loading({
|
||||
text: '正在准备..'
|
||||
});
|
||||
|
@ -244,6 +245,34 @@ export default {
|
|||
window.open(response.data.path, "_blank");
|
||||
}).catch(e => { loading.close() });
|
||||
},
|
||||
batchDelete() {
|
||||
if (this.selects.length === 0) {
|
||||
this.$message({
|
||||
message: '请选择要删除的项',
|
||||
type: 'warning'
|
||||
});
|
||||
return;
|
||||
}else{
|
||||
// 删除前的提示
|
||||
this.$confirm("确认删除记录吗?", "提示", {
|
||||
type: "warning",
|
||||
}).then(() => {
|
||||
console.log(this.selects)
|
||||
this.deleteItems(this.selects);
|
||||
this.getList()});
|
||||
}
|
||||
},
|
||||
deleteItems(ids){
|
||||
let item = {ids:ids};
|
||||
deletesQuestion(item).then(response => {
|
||||
if (response.code == 200) {
|
||||
this.$message.success("删除成功");
|
||||
this.getList();
|
||||
} else {
|
||||
this.$message.error(response.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
changeSort(val) {
|
||||
if (val.order == "ascending") {
|
||||
this.listQuery.ordering = val.prop;
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm('Form')" :loading="submitLoding">保存</el-button>
|
||||
<el-button type="warning" @click="goBack()">返回</el-button>
|
||||
<el-button type="warning" @click="goBack()" v-if="qid==null">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
@ -97,6 +97,7 @@ import { upUrl } from "@/api/file";
|
|||
import { getToken } from "@/utils/auth";
|
||||
export default {
|
||||
components:{ },
|
||||
props:["qid"],
|
||||
data() {
|
||||
return {
|
||||
upHeaders: { Authorization: "JWT " + getToken() },
|
||||
|
@ -135,7 +136,12 @@ export default {
|
|||
watch:{
|
||||
},
|
||||
created() {
|
||||
this.Form.id = this.$route.query.id //接收参数
|
||||
if(this.qid) {
|
||||
this.Form.id = this.qid
|
||||
}else{
|
||||
this.Form.id = this.$route.query.id //接收参数
|
||||
}
|
||||
|
||||
this.getQuestion();
|
||||
this.getQuestioncatAll()
|
||||
},
|
||||
|
@ -169,7 +175,12 @@ export default {
|
|||
type: "success",
|
||||
message: "修改成功!"
|
||||
});
|
||||
this.goBack()
|
||||
if (this.qid){
|
||||
this.$emit("updateQuestion", this.Form)
|
||||
}else{
|
||||
this.goBack()
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -54,12 +54,12 @@
|
|||
icon="el-icon-edit"
|
||||
:disabled="!checkPermission(['paper_update'])"
|
||||
></el-button>
|
||||
<el-button
|
||||
<!-- <el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleClone(scope)"
|
||||
:disabled="!checkPermission(['paper_clone'])"
|
||||
>克隆</el-button>
|
||||
>克隆</el-button> -->
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-row>
|
||||
<el-row :gutter="6">
|
||||
<el-col :span="8">
|
||||
<h3>基本信息</h3>
|
||||
<el-card header="试卷信息">
|
||||
<el-form :model="Form" :rules="rules" ref="Form" label-width="100px" status-icon>
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="Form.name" style="width:80%"></el-input>
|
||||
|
@ -42,9 +42,10 @@
|
|||
<el-button type="warning" @click="goBack()">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<h3>选题信息</h3>
|
||||
<el-card header="试题信息">
|
||||
<!-- <div> -->
|
||||
<div style="display: flex;">
|
||||
<el-button type="primary" @click="handleChoose" icon="el-icon-plus" style="margin-right: 10px;">选择试题</el-button>
|
||||
|
@ -86,34 +87,45 @@
|
|||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<div v-for="(item, index) in questions">
|
||||
<h4>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="handleDelete(index)"
|
||||
icon="el-icon-delete"
|
||||
></el-button>
|
||||
{{ index+1 }} -
|
||||
<draggable v-model="questions">
|
||||
<el-card style="margin-top: 4px" shadow="never" v-for="(item, index) in questions" v-bind:key="index">
|
||||
<div slot="header" style="display: flex;">
|
||||
<span>第{{ index+1 }}题</span>
|
||||
<el-link style="margin-left: auto; margin-right: 8px" type="primary" @click="handleEdit(item, index)" icon="el-icon-edit"></el-link>
|
||||
<el-link style="margin-right: 4px" type="danger" @click="handleDelete(index)" icon="el-icon-remove"></el-link>
|
||||
<el-link style="margin-right: 4px" type="danger" @click="handleDelete2(item, index)" icon="el-icon-delete"></el-link>
|
||||
</div>
|
||||
<div style="font-weight: bold;">
|
||||
<el-tag>{{item.type}}</el-tag>
|
||||
{{ item.name }}
|
||||
<span>(正确答案:{{item.right}})</span>
|
||||
</h4>
|
||||
<div v-for="(value, name) in item.options">{{ name }}: {{ value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size: 14px;" v-for="(value, name) in item.options">{{ name }}: {{ value }}</div>
|
||||
</el-card>
|
||||
</draggable>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<Questionchoose v-bind:chooseVisible="chooseVisible" @closeDg="closeDg" @choseQ="choseQ"></Questionchoose>
|
||||
<el-dialog title="编辑题目" :visible.sync="qDialog">
|
||||
<QuestionUpdate v-if="qDialog" :qid="qid" @updateQuestion="updateQuestion"></QuestionUpdate>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { getQuestioncatList, createPaper, UploadPaper } from "@/api/exam";
|
||||
import { getQuestioncatList, createPaper, UploadPaper, deleteQuestion } from "@/api/exam";
|
||||
import Questionchoose from "@/views/exam/questionChoose";
|
||||
import { upUrl, upHeaders } from "@/api/file";
|
||||
import draggable from 'vuedraggable';
|
||||
import QuestionUpdate from "./questionupdate.vue";
|
||||
export default {
|
||||
components: { Questionchoose },
|
||||
components: { Questionchoose, draggable, QuestionUpdate },
|
||||
data() {
|
||||
return {
|
||||
qDialog: false,
|
||||
qid: null,
|
||||
currentEditIndex: null,
|
||||
questions: [],
|
||||
Form: {
|
||||
name: "",
|
||||
|
@ -164,6 +176,15 @@
|
|||
this.getQuestioncat();
|
||||
},
|
||||
methods: {
|
||||
updateQuestion(data){
|
||||
this.qDialog = false;
|
||||
this.questions[this.currentEditIndex] = data;
|
||||
},
|
||||
handleEdit(item, index) {
|
||||
this.qDialog = true;
|
||||
this.qid = item.id;
|
||||
this.currentEditIndex = index;
|
||||
},
|
||||
getQuestioncat() {
|
||||
getQuestioncatList().then(response => {
|
||||
this.workscopeData = response.data.results;
|
||||
|
@ -180,10 +201,8 @@
|
|||
let that =this;
|
||||
if (res.code == 201){
|
||||
that.impForm.doc_path = res.data.path;
|
||||
console.log(that.impForm)
|
||||
UploadPaper(that.impForm).then(res => {
|
||||
if (res.code == 200) {
|
||||
console.log(res.data)
|
||||
that.questions = res.data;
|
||||
|
||||
that.$message({
|
||||
|
@ -236,6 +255,21 @@
|
|||
handleDelete(val) {
|
||||
this.questions.splice(val, 1);
|
||||
},
|
||||
handleDelete2(item, val){
|
||||
|
||||
deleteQuestion(item.id).then(res=>{
|
||||
console.log(res)
|
||||
if (res.code ==204) {
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "删除成功!"
|
||||
});
|
||||
this.questions.splice(val, 1);
|
||||
}
|
||||
}).catch(res=>{
|
||||
console.log(res)
|
||||
})
|
||||
},
|
||||
calScore() {
|
||||
let danxuan_count = 0,
|
||||
duoxuan_count = 0,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-row>
|
||||
<el-row :gutter="6">
|
||||
<el-col :span="8">
|
||||
<h3>基本信息</h3>
|
||||
<el-card header="试卷信息">
|
||||
<el-form :model="Form" :rules="rules" ref="Form" label-width="100px" status-icon>
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="Form.name" style="width:80%"></el-input>
|
||||
|
@ -42,38 +42,48 @@
|
|||
<el-button type="warning" @click="goBack()">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<h3>选题信息</h3>
|
||||
<el-card header="试题信息">
|
||||
<el-button type="primary" @click="handleChoose" icon="el-icon-plus">选择试题</el-button>
|
||||
<div v-for="(item, index) in questions">
|
||||
<h4>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="handleDelete(index)"
|
||||
icon="el-icon-delete"
|
||||
></el-button>
|
||||
{{ index+1 }} -
|
||||
<draggable v-model="questions">
|
||||
<el-card style="margin-top: 4px" shadow="never" v-for="(item, index) in questions" v-bind:key="index">
|
||||
<div slot="header" style="display: flex;">
|
||||
<span>第{{ index+1 }}题</span>
|
||||
<el-link style="margin-left: auto; margin-right: 8px" type="primary" @click="handleEdit(item, index)" icon="el-icon-edit"></el-link>
|
||||
<el-link style="margin-right: 4px" type="danger" @click="handleDelete(index)" icon="el-icon-delete"></el-link>
|
||||
</div>
|
||||
<div style="font-weight: bold;">
|
||||
<el-tag>{{item.type}}</el-tag>
|
||||
{{ item.name }}
|
||||
<span>(正确答案:{{item.right}})</span>
|
||||
</h4>
|
||||
<div v-for="(value, name) in item.options">{{ name }}: {{ value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size: 14px;" v-for="(value, name) in item.options">{{ name }}: {{ value }}</div>
|
||||
</el-card>
|
||||
</draggable>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<Questionchoose v-bind:chooseVisible="chooseVisible" @closeDg="closeDg" @choseQ="choseQ"></Questionchoose>
|
||||
<el-dialog title="编辑题目" :visible.sync="qDialog">
|
||||
<QuestionUpdate v-if="qDialog" :qid="qid" @updateQuestion="updateQuestion"></QuestionUpdate>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getQuestioncatList, updatePaper,getPaperDetail } from "@/api/exam";
|
||||
import { genTree } from "@/utils";
|
||||
import Questionchoose from "@/views/exam/questionChoose";
|
||||
import draggable from 'vuedraggable';
|
||||
import QuestionUpdate from "./questionupdate.vue";
|
||||
export default {
|
||||
components: { Questionchoose },
|
||||
components: { Questionchoose, draggable, QuestionUpdate },
|
||||
data() {
|
||||
return {
|
||||
qDialog: false,
|
||||
qid: null,
|
||||
currentEditIndex: null,
|
||||
questions: [],
|
||||
Form: {
|
||||
name: "",
|
||||
|
@ -116,6 +126,16 @@
|
|||
this.getPaperDetail();
|
||||
},
|
||||
methods: {
|
||||
updateQuestion(data){
|
||||
this.qDialog = false;
|
||||
data["question"] = data["id"]
|
||||
this.questions[this.currentEditIndex] = data;
|
||||
},
|
||||
handleEdit(item, index) {
|
||||
this.qDialog = true;
|
||||
this.qid = item.question;
|
||||
this.currentEditIndex = index;
|
||||
},
|
||||
getQuestioncat() {
|
||||
getQuestioncatList().then(response => {
|
||||
this.workscopeData = genTree(response.data);
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
:value="item.key"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
<!-- <el-select
|
||||
v-model="listQuery.roles"
|
||||
placeholder="角色"
|
||||
style="width: 90px"
|
||||
|
@ -55,7 +55,7 @@
|
|||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-select> -->
|
||||
<el-input
|
||||
v-model="listQuery.search"
|
||||
placeholder="姓名/邮箱"
|
||||
|
@ -115,16 +115,16 @@
|
|||
</el-form>
|
||||
<div style="text-align: right">
|
||||
<el-button type="danger" @click="close_dialog">取消</el-button>
|
||||
<el-button type="primary" @click="submitUpload" :disabled="saveLoading">确认</el-button>
|
||||
<el-button type="primary" @click="submitUpload">确认</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</div>
|
||||
<div style="margin-top: 10px">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAddUser"
|
||||
>新增</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAddUser"
|
||||
>新增</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="listLoading"
|
||||
:data="userList.results"
|
||||
|
@ -147,7 +147,7 @@
|
|||
scope.row.dept_name
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="header-center" label="角色">
|
||||
<!-- <el-table-column align="header-center" label="角色">
|
||||
<template slot-scope="scope">
|
||||
<el-tag
|
||||
style="margin: 2px"
|
||||
|
@ -158,7 +158,7 @@
|
|||
{{ item.name }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column> -->
|
||||
<el-table-column label="创建日期">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.date_joined }}</span>
|
||||
|
@ -196,6 +196,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="userList.count > 0"
|
||||
:total="userList.count"
|
||||
|
@ -206,6 +207,66 @@
|
|||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
:title="dialogType === 'edit' ? '编辑用户' : '新增用户'"
|
||||
>
|
||||
<el-form
|
||||
ref="Form"
|
||||
:model="user"
|
||||
label-width="80px"
|
||||
label-position="right"
|
||||
:rules="rule1"
|
||||
>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="user.name" placeholder="姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="账户" prop="username">
|
||||
<el-input v-model="user.username" placeholder="账户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属部门" prop="dept">
|
||||
<treeselect
|
||||
v-model="user.dept"
|
||||
:multiple="false"
|
||||
:options="orgData"
|
||||
placeholder="所属部门"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="角色" prop="roles">
|
||||
<el-select
|
||||
v-model="user.roles"
|
||||
multiple
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in roles"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="头像" prop="dept">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
:action="upUrl"
|
||||
accept="image/jpeg, image/gif, image/png, image/bmp"
|
||||
:show-file-list="false"
|
||||
:on-success="handleAvatarSuccess"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:headers="upHeaders"
|
||||
>
|
||||
<img v-if="user.avatar" :src="user.avatar" class="avatar" />
|
||||
<i v-else class="el-icon-plus avatar-uploader-icon" />
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="text-align: right">
|
||||
<el-button type="danger" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirm('Form')">确认</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
|
@ -266,9 +327,12 @@ export default {
|
|||
upHeaders: upHeaders(),
|
||||
upUrl: upUrl(),
|
||||
userList: { count: 0 },
|
||||
roles: [],
|
||||
// roles: [],
|
||||
saveLoading:false,
|
||||
fileList: [],
|
||||
impForm:{
|
||||
file:''
|
||||
},
|
||||
impDialogVisible: false,
|
||||
listLoading: true,
|
||||
downloadUrl: process.env.VUE_APP_BASE_API,
|
||||
|
@ -300,7 +364,7 @@ export default {
|
|||
{ header: '姓名', key: 'name', wpx: 60 },
|
||||
{ header: '账户', key: 'username', wch: 32 },
|
||||
{ header: '部门', key: 'dept_name', width: 30 },
|
||||
{ header: '角色', key: 'roles', width: 70 }
|
||||
// { header: '角色', key: 'roles', width: 70 }
|
||||
],
|
||||
};
|
||||
},
|
||||
|
@ -353,11 +417,9 @@ export default {
|
|||
this.impForm.file = '';
|
||||
},
|
||||
handleUpSuccess(res, file, filelist) {
|
||||
this.Content.file = res.data.id;
|
||||
this.impForm.file = res.data.path;
|
||||
},
|
||||
handleRemove(file, filelist) {
|
||||
this.Content.file = null;
|
||||
this.impForm.file = '';
|
||||
},
|
||||
handlePreview(file) {
|
||||
|
@ -438,12 +500,16 @@ export default {
|
|||
type: "error",
|
||||
})
|
||||
.then(async () => {
|
||||
await deleteUserExam(scope.row.id);
|
||||
this.userList.splice(scope.row.index, 1);
|
||||
this.$message.success("成功");
|
||||
deleteUserExam(scope.row.id).then((res) => {
|
||||
console.log(res);
|
||||
if (res.code >= 200) {
|
||||
// this.userList.splice(scope.row.index, 1);
|
||||
this.$message.success("成功");
|
||||
this.getList();
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
});
|
||||
},
|
||||
async confirm(form) {
|
||||
|
@ -454,9 +520,11 @@ export default {
|
|||
updateUser(this.user.id, this.user).then((res) => {
|
||||
if (res.code >= 200) {
|
||||
this.getList();
|
||||
this.dialogVisible = false;
|
||||
this.dialogVisible = true;
|
||||
this.impForm.file = ''
|
||||
this.$message.success("成功");
|
||||
}
|
||||
};
|
||||
this.impForm.file = ''
|
||||
});
|
||||
} else {
|
||||
createUser(this.user).then((res) => {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.12 on 2024-06-04 09:28
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('exam', '0001_initial'),
|
||||
('edu', '0006_certificate_examrecord'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='certificate',
|
||||
name='examrecord',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cert_er', to='exam.examrecord'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.12 on 2024-06-06 01:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('edu', '0007_alter_certificate_examrecord'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='certificate',
|
||||
name='用户ID',
|
||||
field=models.IntegerField(blank=True, max_length=32, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.2.12 on 2024-06-06 02:26
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('edu', '0008_certificate_用户id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='certificate',
|
||||
name='用户ID',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
|
@ -26,7 +26,8 @@ class Certificate(CommonADModel):
|
|||
是否需要北京标研培训合格 = models.BooleanField(default=False)
|
||||
课程列表 = models.ManyToManyField(Course, blank=True)
|
||||
培训日期 = models.DateField(null=True, blank=True)
|
||||
培训结束日期 = models.DateField(null=True, blank=True)
|
||||
培训结束日期 = models.DateField(null=True, blank=True)
|
||||
发证日期 = models.DateField(null=True, blank=True)
|
||||
证书地址 = models.CharField(max_length=100, null=True, blank=True)
|
||||
examrecord = models.ForeignKey('exam.ExamRecord', on_delete=models.CASCADE, null=True, blank=True)
|
||||
用户ID = models.ForeignKey(null=True, blank=True, to='system.User', on_delete=models.CASCADE)
|
||||
examrecord = models.OneToOneField('exam.ExamRecord', on_delete=models.CASCADE, null=True, blank=True, related_name='cert_er')
|
||||
|
|
|
@ -5,7 +5,7 @@ from openpyxl.styles import Font, Fill
|
|||
import json
|
||||
import os
|
||||
|
||||
def export_question(questions):
|
||||
def export_question(questions:object):
|
||||
'''
|
||||
params: serializer questions
|
||||
return: xlsx path
|
||||
|
@ -24,4 +24,33 @@ def export_question(questions):
|
|||
if not os.path.exists(full_path):
|
||||
os.makedirs(full_path)
|
||||
wb.save(full_path+filename)
|
||||
return path + filename
|
||||
return path + filename
|
||||
|
||||
|
||||
def export_record(records:object):
|
||||
'''
|
||||
params: serializer records
|
||||
return: xlsx path
|
||||
'''
|
||||
wb = Workbook()
|
||||
ws1 = wb.active
|
||||
ws1.title = '答题记录表'
|
||||
ws1.append(['类型', '用户', '是否通过', '得分', '总分', '耗时(时分秒)', '答题时间', '部门'])
|
||||
row = ws1.row_dimensions[1]
|
||||
row.font = Font(bold=True)
|
||||
for i in records:
|
||||
tookformat = get_took_format(i.took)
|
||||
ws1.append([i.type, i.create_by.name, i.is_pass, i.score, i.total_score, str(tookformat), str(i.start_time), i.belong_dept.name])
|
||||
filename = 'records' + datetime.now().strftime("%Y%m%d%H%M%S") +'.xlsx'
|
||||
path = '/media/temp/'
|
||||
full_path = settings.BASE_DIR + '/media/temp/'
|
||||
if not os.path.exists(full_path):
|
||||
os.makedirs(full_path)
|
||||
wb.save(full_path+filename)
|
||||
return path + filename
|
||||
|
||||
|
||||
def get_took_format(took:int):
|
||||
m, s = divmod(took, 60)
|
||||
h, m = divmod(m, 60)
|
||||
return "%02d:%02d:%02d" % (h, m, s)
|
|
@ -1,6 +1,9 @@
|
|||
from django_filters import rest_framework as filters
|
||||
from django.utils import timezone
|
||||
from .models import ExamRecord, Exam
|
||||
from django.db.models import Q
|
||||
class ExamRecordFilter(filters.FilterSet):
|
||||
is_my = filters.BooleanFilter(method='filter_is_my')
|
||||
class Meta:
|
||||
model = ExamRecord
|
||||
fields = {
|
||||
|
@ -8,9 +11,17 @@ class ExamRecordFilter(filters.FilterSet):
|
|||
'is_pass': ['exact'],
|
||||
'type': ['exact'],
|
||||
'is_submited': ['exact'],
|
||||
'exam': ['exact']
|
||||
}
|
||||
def filter_is_my(self, queryset, name, value):
|
||||
if value:
|
||||
user = self.request.user
|
||||
return queryset.filter(create_by=user)
|
||||
return queryset
|
||||
|
||||
class ExamFilter(filters.FilterSet):
|
||||
can_attend = filters.BooleanFilter(method='filter_can_attend')
|
||||
is_my = filters.BooleanFilter(method='filter_is_my')
|
||||
class Meta:
|
||||
model = Exam
|
||||
fields = {
|
||||
|
@ -18,4 +29,17 @@ class ExamFilter(filters.FilterSet):
|
|||
'paper': ['exact'],
|
||||
'code': ['exact'],
|
||||
'is_open': ['exact']
|
||||
}
|
||||
}
|
||||
|
||||
def filter_can_attend(self, queryset, name, value):
|
||||
if value:
|
||||
now = timezone.now()
|
||||
return queryset.filter(open_time__lte=now, close_time__gte=now)| queryset.filter(close_time__isnull=True)
|
||||
return queryset
|
||||
|
||||
def filter_is_my(self, queryset, name, value):
|
||||
if value:
|
||||
user = self.request.user
|
||||
return queryset.filter(Q(participant_user=user)|Q(participant_dep=user.dept)|Q(is_open=True))
|
||||
return queryset
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
from django.db import models
|
||||
from apps.system.models import CommonAModel, CommonBModel
|
||||
from apps.system.models import CommonAModel, CommonADModel, CommonBDModel
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from utils.model import BaseModel
|
||||
from apps.edu.models import Course
|
||||
|
@ -20,7 +20,7 @@ class Questioncat(CommonAModel):
|
|||
return self.questioncat.count()
|
||||
|
||||
|
||||
class Question(CommonAModel):
|
||||
class Question(CommonADModel):
|
||||
type_choices = (
|
||||
('单选', '单选'),
|
||||
('多选', '多选'),
|
||||
|
@ -99,7 +99,7 @@ class Exam(CommonAModel):
|
|||
return self.name
|
||||
|
||||
|
||||
class ExamRecord(CommonBModel):
|
||||
class ExamRecord(CommonBDModel):
|
||||
'''
|
||||
考试记录表
|
||||
'''
|
||||
|
|
|
@ -94,6 +94,6 @@ def interpret_text(start:int, excel_path:str, doc_path:str, field=None):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
doc_path = "C:\code\data\\test.docx"
|
||||
doc_path = "C:\code\data\\判断题.docx"
|
||||
excel_path = "C:\code\data\question.xlsx"
|
||||
interpret_text(3, excel_path, doc_path)
|
|
@ -88,6 +88,7 @@ class ExamListSerializer(ModelSerializer):
|
|||
course_ = CourseSerializer(source='course_name', read_only=True)
|
||||
user_count = serializers.SerializerMethodField()
|
||||
submit_count = serializers.SerializerMethodField()
|
||||
limit = CharField(source='paper.limit', read_only=True)
|
||||
class Meta:
|
||||
model = Exam
|
||||
fields = '__all__'
|
||||
|
@ -132,6 +133,8 @@ class ExamRecordListSerializer(serializers.ModelSerializer):
|
|||
source='exam.name', read_only=True)
|
||||
blong_dept_name = serializers.CharField(
|
||||
source='belong_dept.name', read_only=True)
|
||||
cert_number = serializers.CharField(source='cert_er.证书编号', read_only=True)
|
||||
cert_path = serializers.CharField(source='cert_er.证书地址', read_only=True)
|
||||
class Meta:
|
||||
model = ExamRecord
|
||||
exclude = ('detail',)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.shortcuts import render
|
||||
from rest_framework.viewsets import ModelViewSet, GenericViewSet
|
||||
from rest_framework.mixins import ListModelMixin, DestroyModelMixin, RetrieveModelMixin
|
||||
from apps.exam.exports import export_question
|
||||
from apps.exam.exports import export_question, export_record
|
||||
from apps.exam.models import Question, Questioncat, PaperQuestion
|
||||
from apps.exam.serializers import (QuestionSerializer, QuestioncatSerializer, PaperSerializer, ExamDetailSerializer, ExamRecordDetailSerializer, ExamListSerializer,
|
||||
ExamCreateUpdateSerializer, ExamListSerializer, ExamRecordSubmitSerializer, PaperDetailSerializer, PaperCreateUpdateSerializer, AnswerDetailOutSerializer, ExamRecordListSerializer)
|
||||
|
@ -11,7 +11,7 @@ from rest_framework.permissions import IsAuthenticated
|
|||
from rest_framework.exceptions import ParseError
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from django.conf import settings
|
||||
from apps.exam.models import Paper, Exam, ExamRecord, AnswerDetail
|
||||
from apps.exam.models import Paper, Exam, ExamRecord, AnswerDetail, PaperQuestion
|
||||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
from rest_framework.serializers import Serializer
|
||||
|
@ -20,11 +20,13 @@ from apps.exam.filters import ExamRecordFilter, ExamFilter
|
|||
from datetime import timedelta
|
||||
from apps.system.mixins import CreateUpdateCustomMixin
|
||||
from apps.edu.serializers import CertificateSerializer
|
||||
from apps.edu.models import Certificate
|
||||
from utils.queryset import get_child_queryset2
|
||||
from apps.system.permission import has_permission
|
||||
from apps.exam.parse_word import interpret_text
|
||||
import os
|
||||
import shutil
|
||||
from django.db.models import Q
|
||||
# Create your views here.
|
||||
|
||||
EXCEL_PATH = os.path.join(settings.BASE_DIR, "media/default/question.xlsx")
|
||||
|
@ -63,22 +65,21 @@ class QuestionViewSet(CreateUpdateCustomMixin, ModelViewSet):
|
|||
perms_map = {'get': '*', 'post':'question', 'put':'question', 'delete':'question'}
|
||||
queryset = Question.objects.all()
|
||||
serializer_class = QuestionSerializer
|
||||
filterset_fields = ['level', 'type', 'year']
|
||||
filterset_fields = ['level', 'type', 'year', 'questioncat']
|
||||
search_fields = ['name', 'options', 'resolution']
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""
|
||||
删除题目
|
||||
"""
|
||||
id = kwargs.get('pk', None)
|
||||
# 删除前进行校验,如果存在考试中不允许删除。
|
||||
paperquestion = PaperQuestion.objects.filter(question_id=id).exists()
|
||||
if paperquestion:
|
||||
raise ParseError('此试题存在考试中不允许删除')
|
||||
Question.objects.filter(id=id).delete()
|
||||
return Response("删除成功")
|
||||
|
||||
@action(methods=['get'], detail=False,
|
||||
url_path='export', url_name='export_question', perms_map=[{'get': '*'}], serializer_class=Serializer)
|
||||
def export_question(self, request):
|
||||
"""
|
||||
导出题目
|
||||
|
||||
导出题目
|
||||
"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
path = export_question(queryset)
|
||||
return Response({'path': path})
|
||||
|
||||
@action(methods=['post'], detail=False, url_name='enable_question', perms_map={'post': 'question'}, serializer_class=Serializer)
|
||||
def enable(self, request):
|
||||
"""
|
||||
|
@ -90,6 +91,30 @@ class QuestionViewSet(CreateUpdateCustomMixin, ModelViewSet):
|
|||
if ids:
|
||||
Question.objects.filter(pk__in=ids).update(enabled=True)
|
||||
return Response(status=200)
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map={'*':'question_delete'})
|
||||
def deletes(self, request):
|
||||
"""
|
||||
批量删除
|
||||
"""
|
||||
ids = request.data.get('ids', [])
|
||||
if request.user.is_superuser:
|
||||
# 删除前进行校验,如果存在考试中不允许删除。
|
||||
paperquestion = PaperQuestion.objects.filter(question__id__in=ids).exists()
|
||||
if paperquestion:
|
||||
return Response('此试题存在考试中不允许删除')
|
||||
Question.objects.filter(id__in=ids).update(is_deleted=True)
|
||||
return Response()
|
||||
return Response({'msg':'权限不足'},status=401)
|
||||
|
||||
@action(methods=['get'], detail=False, perms_map={'get':'export_question'})
|
||||
def export(self, request):
|
||||
"""
|
||||
导出题目
|
||||
"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
path = export_question(queryset)
|
||||
return Response({'path': path})
|
||||
|
||||
@action(methods=['post'], detail=False,
|
||||
url_path='import', url_name='import_question', perms_map={'post': 'question'}, serializer_class=Serializer)
|
||||
|
@ -269,6 +294,34 @@ class PaperViewSet(ModelViewSet):
|
|||
PaperQuestion.objects.filter(paper=paper).delete()
|
||||
PaperQuestion.objects.bulk_create(q_list)
|
||||
return Response()
|
||||
|
||||
|
||||
@action(methods=['put'], detail=True, url_path='clone', url_name='clone_paper', perms_map={'put':'clone_paper'})
|
||||
def clone(self, request, pk=None):
|
||||
'''
|
||||
克隆试卷
|
||||
'''
|
||||
paper = self.get_object()
|
||||
obj = Paper()
|
||||
obj.name = '克隆卷-'+paper.name
|
||||
obj.workscope = paper.workscope
|
||||
obj.limit = paper.limit
|
||||
obj.total_score = paper.total_score
|
||||
obj.pass_score = paper.pass_score
|
||||
obj.danxuan_count = paper.danxuan_count
|
||||
obj.danxuan_score = paper.danxuan_score
|
||||
obj.duoxuan_count = paper.duoxuan_count
|
||||
obj.duoxuan_score = paper.duoxuan_score
|
||||
obj.panduan_count = paper.panduan_count
|
||||
obj.panduan_score = paper.panduan_score
|
||||
obj.save()
|
||||
for i in PaperQuestion.objects.filter(paper=paper):
|
||||
o = PaperQuestion()
|
||||
o.paper = obj
|
||||
o.question = i.question
|
||||
o.total_score = i.total_score
|
||||
o.save()
|
||||
return Response(status=200)
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'question'}, serializer_class=Serializer)
|
||||
def upload_paper(self, request):
|
||||
|
@ -276,7 +329,6 @@ class PaperViewSet(ModelViewSet):
|
|||
question_type = request.data.get('question_type')
|
||||
excel_path = settings.BASE_DIR + "/media/default/question.xlsx"
|
||||
doc_path = settings.BASE_DIR + doc_path
|
||||
# excel_path = "C:\code\data\question.xlsx"
|
||||
timenow = timezone.now().strftime('%Y%m%d%H%M%S')
|
||||
question_excel_name = "question_excel_"+timenow
|
||||
question_excel = os.path.join(os.path.dirname(excel_path), question_excel_name)
|
||||
|
@ -425,15 +477,23 @@ class ExamViewSet(CreateUpdateCustomMixin, ModelViewSet):
|
|||
elif self.action in ['retrieve']:
|
||||
return ExamDetailSerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
user = self.request.user
|
||||
if has_permission("exam", user):
|
||||
return qs
|
||||
return qs.filter(Q(participant_user=user)|Q(participant_dep=user.dept)|Q(is_open=True))
|
||||
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
if ExamRecord.objects.filter(exam=instance).exists():
|
||||
raise ParseError('存在考试记录,禁止删除')
|
||||
instance.delete(soft=False)
|
||||
instance.delete()
|
||||
return Response(status=204)
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map=[{'post': '*'}], serializer_class=Serializer, permission_classes = [IsAuthenticated])
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=Serializer, permission_classes = [IsAuthenticated])
|
||||
@transaction.atomic
|
||||
def start(self, request, *args, **kwargs):
|
||||
"""
|
||||
|
@ -441,7 +501,8 @@ class ExamViewSet(CreateUpdateCustomMixin, ModelViewSet):
|
|||
|
||||
开始考试具体题目信息
|
||||
"""
|
||||
exam = self.get_object()
|
||||
# exam = self.get_object()
|
||||
exam = Exam.objects.get(id=kwargs['pk'])
|
||||
# 查询本次考试对应哪些人
|
||||
participants = exam.participant_user.all()
|
||||
participants_ids = [i.id for i in participants]
|
||||
|
@ -489,7 +550,7 @@ class ExamRecordViewSet(ListModelMixin, DestroyModelMixin, RetrieveModelMixin, G
|
|||
考试记录列表和详情
|
||||
"""
|
||||
perms_map = {'get': '*', 'post': '*', 'delete':'examrecord'}
|
||||
queryset = ExamRecord.objects.select_related('create_by')
|
||||
queryset = ExamRecord.objects.select_related('create_by', 'cert_er')
|
||||
serializer_class = ExamRecordListSerializer
|
||||
ordering_fields = ['create_time', 'score', 'took', 'update_time', 'belong_dept']
|
||||
ordering = ['-update_time']
|
||||
|
@ -505,24 +566,25 @@ class ExamRecordViewSet(ListModelMixin, DestroyModelMixin, RetrieveModelMixin, G
|
|||
if has_permission('ctc_manager', self.request.user):
|
||||
return qs
|
||||
# 如果是部门管理员,只能看到自己部门下的考试记录
|
||||
# return qs.filter(belong_dept__in=get_child_queryset2(self.request.user.dept))
|
||||
elif has_permission('exam_manager', self.request.user):
|
||||
return qs.filter(belong_dept__in=get_child_queryset2(self.request.user.dept))
|
||||
# 如果是普通员工,只能看到自己考试记录
|
||||
else:
|
||||
return qs.filter(create_by=self.request.user)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'retrieve':
|
||||
return ExamRecordDetailSerializer
|
||||
now = timezone.now()
|
||||
if now > self.get_object().exam.close_time:
|
||||
return ExamRecordDetailSerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
def perform_destroy(self, instance): # 考试记录物理删除
|
||||
instance.delete(soft=False)
|
||||
instance.delete()
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map=[{'post': '*'}], serializer_class=Serializer, permission_classes = [IsAuthenticated])
|
||||
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer, permission_classes = [IsAuthenticated])
|
||||
def clear(self, request, pk=None):
|
||||
"""
|
||||
清除七日前未提交的考试记录
|
||||
|
||||
清除七日前未提交的考试记录
|
||||
"""
|
||||
now = timezone.now
|
||||
|
@ -530,19 +592,19 @@ class ExamRecordViewSet(ListModelMixin, DestroyModelMixin, RetrieveModelMixin, G
|
|||
ExamRecord.objects.filter(create_time__lte=days7_ago, is_submited=False).delete(soft=False)
|
||||
return Response(status=False)
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def self(self, request, pk=None):
|
||||
'''
|
||||
个人考试记录
|
||||
|
||||
个人考试记录
|
||||
'''
|
||||
queryset = ExamRecord.objects.filter(create_by=request.user).order_by('-update_time')
|
||||
page = self.paginate_queryset(queryset)
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
@action(methods=['get'], detail=False,
|
||||
url_path='export', url_name='export_record', perms_map={'*': 'export_ansrecord'}, serializer_class=Serializer)
|
||||
def export(self, request):
|
||||
"""
|
||||
导出答题记录
|
||||
"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
path = export_record(queryset)
|
||||
return Response({'path': path})
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map=[{'post': '*'}], serializer_class=ExamRecordSubmitSerializer, permission_classes = [IsAuthenticated])
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=ExamRecordSubmitSerializer, permission_classes = [IsAuthenticated])
|
||||
@transaction.atomic
|
||||
def submit(self, request, pk=None):
|
||||
'''
|
||||
|
@ -594,13 +656,23 @@ class ExamRecordViewSet(ListModelMixin, DestroyModelMixin, RetrieveModelMixin, G
|
|||
now_data = datetime.now()
|
||||
course = exam.course_name.all()
|
||||
courese_ids = [i.id for i in course]
|
||||
current_date = now_data.strftime('%Y-%m-%d')
|
||||
current_date = now_data.strftime('%Y-%m-%d')
|
||||
cer_number = now_data.strftime('%Y%m%d')
|
||||
# 查询证明编号创建时间为最后一个
|
||||
cer = Certificate.objects.latest('证书编号')
|
||||
if cer:
|
||||
cer_number = int(cer.证书编号[5:]) + 1
|
||||
cer_number = 'CTCZL' + str(cer_number)
|
||||
data_dict = {
|
||||
'姓名': request.user.name,
|
||||
'证书编号': 'CTCZL'+ current_date,
|
||||
'用户ID': request.user.id,
|
||||
'证书编号': cer_number,
|
||||
'证书方案': '202312',
|
||||
'单位名称': request.user.dept.name,
|
||||
'所属单位': '国检测试控股集团'+request.user.dept.name,
|
||||
'发证日期': current_date,
|
||||
'培训日期':current_date,
|
||||
'培训结束日期': current_date,
|
||||
'课程列表': courese_ids,
|
||||
'examrecord': er.id,
|
||||
}
|
||||
|
@ -610,7 +682,7 @@ class ExamRecordViewSet(ListModelMixin, DestroyModelMixin, RetrieveModelMixin, G
|
|||
|
||||
er.took = (now - er.create_time).total_seconds()
|
||||
er.end_time = now
|
||||
er.belong_dept=request.user.dept.id
|
||||
er.belong_dept=request.user.dept
|
||||
er.is_submited = True
|
||||
er.save()
|
||||
return Response(ExamRecordListSerializer(instance=er).data)
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.12 on 2024-06-04 09:28
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('information', '0047_alter_qualification_change_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qualification',
|
||||
name='change_date',
|
||||
field=models.DateField(blank=True, default=datetime.datetime(2024, 6, 4, 9, 28, 52, 579869, tzinfo=utc), null=True, verbose_name='变更日期'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.12 on 2024-06-06 01:44
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('information', '0048_alter_qualification_change_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qualification',
|
||||
name='change_date',
|
||||
field=models.DateField(blank=True, default=datetime.datetime(2024, 6, 6, 1, 44, 19, 206626, tzinfo=utc), null=True, verbose_name='变更日期'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.12 on 2024-06-06 02:26
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('information', '0049_alter_qualification_change_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qualification',
|
||||
name='change_date',
|
||||
field=models.DateField(blank=True, default=datetime.datetime(2024, 6, 6, 2, 26, 43, 23224, tzinfo=utc), null=True, verbose_name='变更日期'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.12 on 2024-06-14 05:35
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('information', '0050_alter_qualification_change_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qualification',
|
||||
name='change_date',
|
||||
field=models.DateField(blank=True, default=datetime.datetime(2024, 6, 14, 5, 35, 33, 230280, tzinfo=utc), null=True, verbose_name='变更日期'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.12 on 2024-06-14 06:51
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('information', '0051_alter_qualification_change_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qualification',
|
||||
name='change_date',
|
||||
field=models.DateField(blank=True, default=datetime.datetime(2024, 6, 14, 6, 51, 53, 747831, tzinfo=utc), null=True, verbose_name='变更日期'),
|
||||
),
|
||||
]
|
|
@ -100,8 +100,6 @@ class ImpMixin:
|
|||
if 'file' not in request.data:
|
||||
raise ParseError('请提供文件')
|
||||
path = request.data['file']
|
||||
print(path, "---------ssss")
|
||||
|
||||
if not str(path).endswith('.xlsx'):
|
||||
raise ParseError('请提供xlsx格式文件')
|
||||
fullpath = settings.BASE_DIR + str(path)
|
||||
|
@ -111,7 +109,7 @@ class ImpMixin:
|
|||
data_list = self.build_data(sheet, start)
|
||||
serializer = mySerializer(data=data_list, many=True, context={'request': request})
|
||||
if serializer.is_valid():
|
||||
serializer.save(create_by=request.user, belong_dept=request.user.dept)
|
||||
serializer.save()
|
||||
else:
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
@ -335,6 +333,8 @@ class UserExamViewset(ImpMixin, ModelViewSet):
|
|||
else:
|
||||
# password = make_password(''.join(random.sample(string.ascii_letters + string.digits, 8)))
|
||||
password = make_password('0000')
|
||||
exam_role = Role.objects.get(name='考试')
|
||||
request.data['roles'] = [exam_role.id]
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save(password=password)
|
||||
|
@ -347,12 +347,14 @@ class UserExamViewset(ImpMixin, ModelViewSet):
|
|||
return Response({'msg': '考试角色不存在'})
|
||||
for row in sheet.iter_rows(min_row=start, values_only=True): # 假设第一行是表头,从第二行开始读取数据
|
||||
if row[0] is not None:
|
||||
dept = Organization.objects.get(name=row[3])
|
||||
if not dept:
|
||||
return Response({'msg': '部门不存在'})
|
||||
dept = Organization.objects.get(name=row[2])
|
||||
user_depts = get_child_queryset2(self.request.user.dept).order_by('sort')
|
||||
depts = any(i.name==dept.name for i in user_depts)
|
||||
if depts is False:
|
||||
return Response({'msg': f'本公司下不存在此部门{row[2]}'})
|
||||
serializer_data = {
|
||||
'name': row[1],
|
||||
'username':row[2],
|
||||
'name': row[0],
|
||||
'username':row[1],
|
||||
'dept':dept.id,
|
||||
'roles':[exam_role.id],
|
||||
'avatar': "/media/default/avatar.png"
|
||||
|
@ -510,6 +512,7 @@ class UserViewSet(PageOrNot, ModelViewSet):
|
|||
while sheet['b'+str(i)].value:
|
||||
name = sheet['b'+str(i)].value
|
||||
email = sheet['e'+str(i)].value
|
||||
dept = Organization.objects.get(name=sheet['j'+str(i)].value)
|
||||
if not User.objects.filter(username=email).exists():
|
||||
user = User.objects.create(name=name,
|
||||
username=email,
|
||||
|
@ -517,7 +520,7 @@ class UserViewSet(PageOrNot, ModelViewSet):
|
|||
dept=dept)
|
||||
else:
|
||||
user = User.objects.get(username=email)
|
||||
dept = Organization.objects.get(name=sheet['j'+str(i)].value)
|
||||
|
||||
if sheet['f'+str(i)].value:
|
||||
user.roles.add(role1)
|
||||
if sheet['g'+str(i)].value:
|
||||
|
|
|
@ -28,6 +28,4 @@ DATABASES = {
|
|||
# 'HOST':'1.203.161.101',
|
||||
'PORT': '5432',
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue