正式考试完成

This commit is contained in:
caoqianming 2021-03-21 12:47:42 +08:00
parent 94ee6f844f
commit 27123fb3ec
22 changed files with 308 additions and 37 deletions

View File

@ -132,4 +132,12 @@ export function exportTest(query) {
method: 'get',
params: query
})
}
export function exportwTest(id,data) {
return request({
url: `/examtest/examtest/${id}/exportw/`,
method: 'post',
data: data
})
}

View File

@ -18,7 +18,7 @@
</el-select>
<el-select
v-model="listQuery.type"
placeholder="考类型"
placeholder="类型"
clearable
style="width: 200px"
class="filter-item"
@ -85,6 +85,9 @@
<el-table-column align="left" label="押题卷">
<template slot-scope="scope">{{ scope.row.paper_name }}</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="得分" sortable='custom' prop="score">
<template slot-scope="scope">{{ scope.row.score }}</template>
</el-table-column>
@ -94,14 +97,20 @@
<el-table-column align="left" label="答题时间">
<template slot-scope="scope">{{ scope.row.start_time }}</template>
</el-table-column>
<el-table-column align="center" label="操作">
<el-table-column align="center" label="操作" fixed="right">
<template slot-scope="scope">
<el-button
v-if="scope.row.type=='正式考试'"
type="primary"
size="small"
@click="handleDetail(scope)"
icon="el-icon-more"
></el-button>
@click="handleExport(scope)"
>生成Word</el-button>
<el-button
v-if="scope.row.type=='正式考试'"
type="warning"
size="small"
@click="handleExport2(scope)"
>重新生成</el-button>
</template>
</el-table-column>
</el-table>
@ -116,7 +125,7 @@
</template>
<script>
import { getExamTestlist, exportTest } from "@/api/examtest";
import { getExamTestlist, exportTest, exportwTest } from "@/api/examtest";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination";
@ -138,6 +147,7 @@ export default {
typeOptions: [
{ key: "自助模考", label: "自助模考", value: "自助模考" },
{ key: "押卷模考", label: "押卷模考", value: "押卷模考"},
{ key: "正式考试", label: "正式考试", value: "正式考试"},
],
passOptions: [
{ key: true, label: "通过", value: true },
@ -186,10 +196,19 @@ export default {
value:'setTimeRange',
},
created() {
this.getList();
this.getQuery();
},
methods: {
checkPermission,
getQuery() {
if(this.$route.params.exam){
this.listQuery.exam = this.$route.params.exam;
this.getList()
}else{
this.getList()
}
},
getList() {
this.listLoading = true;
getExamTestlist(this.listQuery).then(response => {
@ -210,9 +229,17 @@ export default {
this.value = []
this.getList();
},
handleDetail(scope) {
// this.dialogVisible = true
// this.question = scope.row
handleExport(scope) {
const loading = this.$loading({text: '正在生成word...',});
exportwTest(scope.row.id).then(res=>{
loading.close()
}).catch(e=>{loading.close()})
},
handleExport2(scope) {
const loading = this.$loading({text: '正在重新生成word...',});
exportwTest(scope.row.id, {anew:true}).then(res=>{
loading.close()
}).catch(e=>{loading.close()})
},
exportTest() {
const loading = this.$loading();

View File

@ -242,6 +242,7 @@ export default {
});
},
handleView(scope){
this.$router.push({name:'ExamTest', params:{exam:scope.row.id}})
},
async confirmexam(form) {

View File

@ -69,9 +69,9 @@ App({
globalData: {
userInfo: {},
userinfo: {}, // 服务器传回的消费者信息
//host: 'https://apitest.ahctc.cn',
host: 'https://apitest.ahctc.cn',
mediahost: 'https://apitest.ahctc.cn',
host: 'http://127.0.0.1:8000',
//host: 'http://127.0.0.1:8000',
//mediahost: 'http://127.0.0.1:8000',
token : '',
}

View File

@ -30,7 +30,8 @@
"pages/material/video",
"pages/qtest/form",
"pages/main/start",
"pages/exam/index"
"pages/exam/index",
"pages/exam/note"
],
"window": {
"backgroundTextStyle": "light",

View File

@ -34,10 +34,14 @@ Page({
})
let exam = res.data.exam
api.request(`/examtest/exam/${exam}/init/`,'POST', data).then(res=>{
console.log(res.data)
wx.hideLoading({
success: (res) => {},
})
wx.hideLoading()
try {
wx.setStorageSync('examtest', res.data)
} catch (e) { }
wx.navigateTo({
url: '/pages/exam/note',
})
}).catch(e=>{
wx.hideLoading({
success: (res) => {},
@ -55,7 +59,11 @@ Page({
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
wx.showLoading({
title: '获取信息...',
})
api.request('/crm/consumer/info/','GET').then(res=>{
wx.hideLoading()
getApp().globalData.userinfo = res.data.userinfo
this.setData(
{
@ -65,7 +73,7 @@ Page({
},
}
)
})
}).catch(e=>{wx.hideLoading()})
},
/**

View File

@ -32,9 +32,8 @@
<label class="weui-label">部门</label>
</view>
<view class="weui-cell__bd">
<span wx:if="{{userinfo.deptname}}">{{userinfo.deptname}}</span>
<input wx:else class="weui-input" placeholder="填写您的部门" placeholder-class="weui-input__placeholder" bindinput="deptInput" >
{{userinfo.deptname}}
<!-- <span wx:if="{{userinfo.deptname}}">{{userinfo.deptname}}</span> -->
<input class="weui-input" placeholder="填写您的部门" placeholder-class="weui-input__placeholder" bindinput="deptInput" value="{{form.deptname}}">
</input>
</view>
</view>

View File

@ -0,0 +1,78 @@
// pages/exam/note.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
try {
var value = wx.getStorageSync('examtest')
if (value) {
let examtest = value
delete examtest['questions']
this.setData(examtest)
}
} catch (e) { }
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
},
startTest: function () {
wx.reLaunch({
url: '/pages/test/test',
})
},
})

View File

@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "考试须知"
}

View File

@ -0,0 +1,24 @@
<view class="page">
<view class="page__bd">
<view class="weui-article">
<view class="weui-article__h1" style="text-align:center">正式考试须知</view>
<view class="weui-article__h2">1.考试信息</view>
<view>名称:<span style="color:blue">{{exam_.name}}</span></view>
<view>监考人:<span style="color:blue">{{exam_.proctor_name}}/{{exam_.proctor_phone}}</span></view>
<view class="weui-article__h2">2.试卷信息</view>
<view>答卷时长:<span style="color:red">{{limit}}分钟</span></view>
<view wx:if="{{danxuan_count>0}}">{{danxuan_count}}道单选题,每题{{danxuan_score}}分</view>
<view wx:if="{{duoxuan_count>0}}">{{duoxuan_count}}道多选题,每题{{duoxuan_score}}分</view>
<view wx:if="{{panduan_count>0}}">{{panduan_count}}道判断题,每题{{panduan_score}}分</view>
<view >多选题漏选每个正确选项得1分,错选0分</view>
<view >满分{{total_score}};<span style="color:blue">{{pass_score}}</span>以上通过</view>
<view class="weui-article__h2">3.答题须知</view>
<view style="color:red">进入答题后请不要后退或返回桌面</view>
<view>用户可点击上一题/下一题进行切换答题</view>
<view>可点击答题卡复查</view>
<view>请合理安排时间答题,可提前交卷,超时会自动提交</view>
</view>
<a class="weui-btn weui-btn_primary" bindtap="startTest">开始考试</a>
</view>
</view>

View File

@ -0,0 +1 @@
/* pages/exam/note.wxss */

View File

@ -18,7 +18,12 @@ Page({
onLoad: function (options) {
var that = this
var query = {'examtest':options.id}
wx.showLoading({
title: '加载中...',
mask:true
})
api.request('/examtest/answerdetail/', 'GET', query).then(res => {
wx.hideLoading()
for (var i = 0; i < res.data.length; i++) {
if(res.data[i].img){
res.data[i].img = getApp().globalData.mediahost + res.data[i].img
@ -29,7 +34,7 @@ Page({
that.setData({
tmtotal:res.data.length
})
})
}).catch(e=>{wx.hideLoading()})
try {
const res = wx.getSystemInfoSync()
that.setData({

View File

@ -1,5 +1,6 @@
// pages/lianxi/index.js
const api = require("../../utils/request.js");
var util = require('../../utils/util.js')
Page({
/**
@ -36,6 +37,9 @@ Page({
getList: function () {
var that = this
api.request('/examtest/examtest/self/', 'GET', that.data.query).then(res => {
for(var i=0;i<res.data.results.length;i++){
res.data.results[i].took = util.formatSecond(res.data.results[i].took)
}
if (that.data.query.page == 1) {
that.data.results = res.data.results
} else {

View File

@ -7,7 +7,8 @@
<view class="weui-media-box__bd weui-media-box__bd_in-appmsg">
<view class="weui-media-box__title">{{item.name}}</view>
<view class="weui-media-box__desc">
<span style="font-weight:bold;color:darkblue">{{item.type}}</span>
<span style="font-weight:bold;color:brown;" wx:if="{{item.type=='正式考试'}}">{{item.type}}</span>
<span style="font-weight:bold;color:darkblue" wx:else>{{item.type}}</span>
<span>-</span>
<span style="font-weight:bold;color:green;" wx:if="{{item.is_pass}}">通过</span>
<span style="font-weight:bold;color:red;" wx:else>未通过</span>
@ -18,7 +19,7 @@
<span style="font-weight:bold">{{item.total_score}})</span>
</view>
<view class="weui-media-box__desc">
耗时:{{item.took}}s
耗时:{{item.took}}
开始答题:{{item.start_time}}
</view>
</view>

View File

@ -150,6 +150,9 @@ Page({
for(var i=0;i<tm_current.user_answer.length;i++){
if(tm_current.right.indexOf(tm_current.user_answer[i])!=-1){
score = score + 1
if(score==2){
break;
}
}else{
score = 0
break;

View File

@ -2,8 +2,13 @@ from openpyxl.workbook import Workbook
from django.conf import settings
from datetime import datetime
from openpyxl.styles import Font, Fill
from rest_framework.serializers import Serializer
from .models import ExamTest, AnswerDetail
from django.http import HttpResponse
from docxtpl import DocxTemplate
from io import BytesIO
from .serializers import ExamTestDetailSerializer
import os
BASE_DIR = settings.BASE_DIR
def export_test(tests):
@ -23,3 +28,24 @@ def export_test(tests):
path = '/media/export/' + filename
wb.save((BASE_DIR + path).replace('\\', '/'))
return path
def exportw_test(obj, bool):
"""
导出个人考试记录word版本
"""
filename = obj.exam.name + '-' + obj.consumer.name + '-' + obj.consumer.username + '.doc'
path = '/media/export/' + filename
fullpath = BASE_DIR + path
if bool or (not os.path.exists(fullpath)):
data = ExamTestDetailSerializer(instance=obj).data
#开始生成word
doc = DocxTemplate(BASE_DIR + "/tmp/examtest.docx")
doc.render(data)
doc.save(fullpath)
# output = BytesIO()
# doc.save(output)
# output.seek(0)
# res = HttpResponse(content_type='application/msword')
# res['Content-Disposition'] = 'attachment;filename='+filename+'.docx'
# res.write(output.getvalue())
return path

View File

@ -0,0 +1,25 @@
# Generated by Django 3.0.4 on 2021-03-21 01:40
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('question', '0012_question_img'),
('examtest', '0025_auto_20210314_1716'),
]
operations = [
migrations.AlterField(
model_name='answerdetail',
name='examtest',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answerdetail_examtest', to='examtest.ExamTest'),
),
migrations.AlterField(
model_name='examtest',
name='detail',
field=models.ManyToManyField(through='examtest.AnswerDetail', to='question.Question', verbose_name='答题记录'),
),
]

View File

@ -20,7 +20,8 @@ class Exam(CommonModel):
proctor_phone = models.CharField('监考人联系方式', max_length=100)
create_admin = models.ForeignKey(UserProfile, on_delete=models.SET_NULL, null=True, blank=True, related_name='exam_create_admin')
def __str__(self):
return self.name
class ExamTest(CommonModel):
@ -43,7 +44,7 @@ class ExamTest(CommonModel):
took = models.IntegerField(default=0, verbose_name='耗时(秒)')
start_time = models.DateTimeField(verbose_name='开始答题时间')
end_time = models.DateTimeField(verbose_name='结束答题时间')
detail = models.ManyToManyField(Question, related_name='答题记录', through='AnswerDetail')
detail = models.ManyToManyField(Question, verbose_name='答题记录', through='AnswerDetail')
is_pass = models.BooleanField(default=True, verbose_name='是否通过')
exam = models.ForeignKey(Exam, verbose_name='关联的正式考试', null=True, blank=True, related_name='examtest_exam', on_delete= models.SET_NULL)
@ -53,7 +54,7 @@ class ExamTest(CommonModel):
class AnswerDetail(SoftCommonModel):
examtest = models.ForeignKey(ExamTest, on_delete=models.CASCADE)
examtest = models.ForeignKey(ExamTest, on_delete=models.CASCADE, related_name='answerdetail_examtest')
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user_answer = JSONField(null=True,blank=True)
score = models.FloatField(default=0, verbose_name='本题得分')

View File

@ -61,6 +61,7 @@ class ExamTestListSerializer(serializers.ModelSerializer):
end_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
workscope_name = serializers.StringRelatedField(source='workscope', read_only=True)
paper_name = serializers.StringRelatedField(source='paper', read_only=True)
exam_name = serializers.StringRelatedField(source='exam', read_only=True)
consumer_name = serializers.SerializerMethodField()
took_format = serializers.SerializerMethodField()
consumer_company_name = serializers.SerializerMethodField()
@ -83,7 +84,7 @@ class ExamTestListSerializer(serializers.ModelSerializer):
@staticmethod
def setup_eager_loading(queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.select_related('consumer','paper', 'workscope')
queryset = queryset.select_related('consumer','paper', 'workscope', 'exam')
return queryset
from question.serializers import QuestionSerializer
class AnswerDetailSerializer(serializers.ModelSerializer):
@ -173,4 +174,45 @@ class ExamListSerializer(serializers.ModelSerializer):
workscope_name = serializers.StringRelatedField(source='workscope')
class Meta:
model = Exam
fields = '__all__'
fields = '__all__'
class ExamSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Exam
exclude = ('create_admin',)
class ConsumerSimpleSerializer(serializers.ModelSerializer):
comanyname = serializers.StringRelatedField(source='company', read_only=True)
class Meta:
model = Consumer
fields = ['id', 'name', 'ID_number1', 'comanyname', 'deptname', 'username']
class ExamTestDetailSerializer(serializers.ModelSerializer):
"""
考试详情序列化(导出用)
"""
workscope_name = serializers.StringRelatedField(source='workscope', read_only=True)
paper_name = serializers.StringRelatedField(source='paper', )
took_format = serializers.SerializerMethodField()
detail_ = serializers.SerializerMethodField()
exam_ = ExamSimpleSerializer(source='exam', read_only=True)
consumer_ = ConsumerSimpleSerializer(source='consumer', read_only=True)
class Meta:
model = ExamTest
exclude = ('detail',)
def get_took_format(self, obj):
m, s = divmod(obj.took, 60)
h, m = divmod(m, 60)
return "%02d:%02d:%02d" % (h, m, s)
def get_detail_(self, obj):
objs = obj.answerdetail_examtest.order_by('question__type')
return AnswerDetailSerializer(instance=objs, many=True).data
@staticmethod
def setup_eager_loading(queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.select_related('consumer','paper', 'workscope', 'exam')
queryset = queryset.prefetch_related('answerdetail_examtest')
return queryset

View File

@ -2,6 +2,7 @@ from datetime import datetime
from django.db.models import Avg
from django.db.models.query import QuerySet
from django.utils.translation import get_language_from_request
from django_filters.rest_framework import DjangoFilterBackend
from openpyxl import Workbook, load_workbook
from rest_framework import status
@ -10,6 +11,7 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
@ -20,14 +22,14 @@ from server import settings
from utils.custom import CommonPagination
from utils.mixins import OptimizationMixin
from .exports import export_test
from .exports import export_test, exportw_test
from .models import AnswerDetail, Banner, ExamTest, Exam
from .models_paper import Paper, PaperQuestions, TestRule, WorkScope
from .serializers import (
AnswerDetailCreateSerializer, AnswerDetailSerializer, BannerSerializer,
ExamTestListSerializer, MoniTestSerializer, PaperDetailSerializer,
PaperQuestionsCreateSerializer, PaperSerializer, TestRuleSerializer,
WorkScopeSerializer, ExamCreateUpdateSerializer, ExamListSerializer)
WorkScopeSerializer, ExamCreateUpdateSerializer, ExamListSerializer, ExamTestDetailSerializer)
from django.utils import timezone
# Create your views here.
@ -77,8 +79,6 @@ class ExamViewSet(ModelViewSet):
"""
参加考试
"""
if request.user.role.name in ['游客', '注册用户']:
return Response({'error':'无权参加考试,请联系老师'})
if request.data.get('deptname', None):
request.user.deptname = request.data['deptname']
request.user.save()
@ -87,6 +87,8 @@ class ExamViewSet(ModelViewSet):
now = timezone.now()
try:
exam = Exam.objects.get(code=code, opentime__lt=now, closetime__gt=now)
if ExamTest.objects.filter(exam=exam, consumer=request.user).exists():
return Response({'error':'您已参加过该场考试'})
return Response({'exam':exam.pk})
except:
return Response({'error':'考试编号不存在'})
@ -103,7 +105,8 @@ class ExamViewSet(ModelViewSet):
if workscope.name in ['医学Ⅲ类', '非医学Ⅲ类']:
ret['name'] = obj.name
ret['type'] = '正式考试' # 正式考试
ret['exam'] = ExamListSerializer(instance=obj).data
ret['exam'] = kwargs['pk']
ret['exam_'] = ExamListSerializer(instance=obj).data
ret['workscope'] = workscope.id
ret['limit'] = 60
ret['total_score'] = 120
@ -347,7 +350,7 @@ class ExamTestViewSet(ModelViewSet):
ordering = ['-create_time']
search_fields = ('consumer__name', 'consumer__company__name')
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['type','is_pass']
filterset_fields = ['type','is_pass', 'exam']
def get_queryset(self):
assert self.queryset is not None, (
@ -431,6 +434,16 @@ class ExamTestViewSet(ModelViewSet):
serializer = ExamTestListSerializer(instance=queryset, many=True)
path = export_test(serializer.data)
return Response({'path': path})
@action(methods=['post'], detail = True ,perms_map=[{'post':'export_test'}])
def exportw(self, request, *args, **kwargs):
obj = self.get_object()
if 'anew' in request.data and request.data['anew']:
# 是否需要重新生成
path = exportw_test(obj, True)
else:
path = exportw_test(obj, False)
# return res
class PaperViewSet(ModelViewSet):
"""

Binary file not shown.

Binary file not shown.