Merge branch 'master' of https://e.coding.net/ctcdevteam/cma_search
This commit is contained in:
commit
6a9fd2207c
|
@ -3,8 +3,8 @@ ENV = 'development'
|
||||||
|
|
||||||
# base api
|
# base api
|
||||||
#VUE_APP_BASE_API = 'http://10.0.11.127:8000/api'
|
#VUE_APP_BASE_API = 'http://10.0.11.127:8000/api'
|
||||||
#VUE_APP_BASE_API = 'http://127.0.0.1:8000/api'
|
VUE_APP_BASE_API = 'http://127.0.0.1:2222/api'
|
||||||
VUE_APP_BASE_API = 'https://testsearch.ctc.ac.cn/api'
|
#VUE_APP_BASE_API = 'https://testsearch.ctc.ac.cn/api'
|
||||||
|
|
||||||
#VUE_APP_BASE_API = 'http://47.95.0.242:9101/api'
|
#VUE_APP_BASE_API = 'http://47.95.0.242:9101/api'
|
||||||
|
|
||||||
|
|
|
@ -60,4 +60,5 @@
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "uview-ui/index.scss";
|
@import "uview-ui/index.scss";
|
||||||
@import "common/demo.scss";
|
@import "common/demo.scss";
|
||||||
|
@import "common/uni-ui.scss";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -44,6 +44,10 @@ const install = (Vue, vm) => {
|
||||||
let getVideos = (params={})=>vm.$u.get('/vod/video/', params);//点播视频
|
let getVideos = (params={})=>vm.$u.get('/vod/video/', params);//点播视频
|
||||||
let getDickey = (params={})=>vm.$u.get('/system/dict/', params);//查询字典
|
let getDickey = (params={})=>vm.$u.get('/system/dict/', params);//查询字典
|
||||||
let putMyVideoView =(id)=>vm.$u.put(`/vod/video/${id}/myview/`);//更新本人观看信息
|
let putMyVideoView =(id)=>vm.$u.put(`/vod/video/${id}/myview/`);//更新本人观看信息
|
||||||
|
|
||||||
|
|
||||||
|
//考试有关
|
||||||
|
let getExamList = (params={})=>vm.$u.get('/exam/exam/', params);//考试列表
|
||||||
vm.$u.api = {getUserInfo,
|
vm.$u.api = {getUserInfo,
|
||||||
getCode,
|
getCode,
|
||||||
codeLogin,
|
codeLogin,
|
||||||
|
@ -64,7 +68,9 @@ const install = (Vue, vm) => {
|
||||||
getDocument,
|
getDocument,
|
||||||
getVideos,
|
getVideos,
|
||||||
getDickey,
|
getDickey,
|
||||||
putMyVideoView
|
putMyVideoView,
|
||||||
|
|
||||||
|
getExamList
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
.uni-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-flex-row {
|
||||||
|
@extend .uni-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-flex-column {
|
||||||
|
@extend .uni-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-color-gary {
|
||||||
|
color: #3b4144;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题 */
|
||||||
|
.uni-title {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: $uni-spacing-col-base;
|
||||||
|
font-size: $uni-font-size-lg;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3b4144;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-title-sub {
|
||||||
|
display: flex;
|
||||||
|
// margin-bottom: $uni-spacing-col-base;
|
||||||
|
font-size: $uni-font-size-base;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #3b4144;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 描述 额外文本 */
|
||||||
|
.uni-note {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: #999;
|
||||||
|
font-size: $uni-font-size-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表内容 */
|
||||||
|
.uni-list-box {
|
||||||
|
@extend .uni-flex-row;
|
||||||
|
flex: 1;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 略缩图 */
|
||||||
|
.uni-thumb {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: $uni-spacing-row-base;
|
||||||
|
width: 125px;
|
||||||
|
height: 75px;
|
||||||
|
border-radius: $uni-border-radius-lg;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px #f5f5f5 solid;
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-media-box {
|
||||||
|
@extend .uni-flex-row;
|
||||||
|
// margin-bottom: $uni-spacing-col-base;
|
||||||
|
border-radius: $uni-border-radius-lg;
|
||||||
|
overflow: hidden;
|
||||||
|
.uni-thumb {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
flex-shrink: 1;
|
||||||
|
width: 33%;
|
||||||
|
border-radius:0;
|
||||||
|
&:first-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容 */
|
||||||
|
.uni-content {
|
||||||
|
@extend .uni-flex-column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表footer */
|
||||||
|
.uni-footer {
|
||||||
|
@extend .uni-flex-row;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-top: $uni-spacing-col-lg;
|
||||||
|
}
|
||||||
|
.uni-footer-text {
|
||||||
|
font-size: $uni-font-size-sm;
|
||||||
|
color: $uni-text-color-grey;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签 */
|
||||||
|
|
||||||
|
.uni-tag {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 5px;
|
||||||
|
border: 1px $uni-border-color solid;
|
||||||
|
margin-right: $uni-spacing-row-sm;
|
||||||
|
border-radius: $uni-border-radius-base;
|
||||||
|
background: $uni-bg-color-grey;
|
||||||
|
color: $uni-text-color;
|
||||||
|
font-size: $uni-font-size-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 链接 */
|
||||||
|
.uni-link {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: $uni-text-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
|
@ -124,7 +124,52 @@
|
||||||
"path" : "pages/vod/video",
|
"path" : "pages/vod/video",
|
||||||
"style" :
|
"style" :
|
||||||
{
|
{
|
||||||
"navigationBarTitleText": "云点播",
|
"navigationBarTitleText": "视频观看",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"path" : "pages/exam/index",
|
||||||
|
"style" :
|
||||||
|
{
|
||||||
|
"navigationBarTitleText": "考试列表",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"path" : "pages/exam/preview",
|
||||||
|
"style" :
|
||||||
|
{
|
||||||
|
"navigationBarTitleText": "考试须知",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"path" : "pages/exam/main",
|
||||||
|
"style" :
|
||||||
|
{
|
||||||
|
"navigationBarTitleText": "答题中",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"path" : "pages/exam/record",
|
||||||
|
"style" :
|
||||||
|
{
|
||||||
|
"navigationBarTitleText": "答题记录",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"path" : "pages/exam/result",
|
||||||
|
"style" :
|
||||||
|
{
|
||||||
|
"navigationBarTitleText": "答题结束",
|
||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,12 +192,18 @@
|
||||||
"selectedIconPath": "static/common/homec.png",
|
"selectedIconPath": "static/common/homec.png",
|
||||||
"text": "主页"
|
"text": "主页"
|
||||||
},
|
},
|
||||||
/* {
|
{
|
||||||
|
"pagePath": "pages/exam/index",
|
||||||
|
"iconPath": "static/common/play.png",
|
||||||
|
"selectedIconPath": "static/common/playc.png",
|
||||||
|
"text": "答题"
|
||||||
|
},
|
||||||
|
{
|
||||||
"pagePath": "pages/vod/video",
|
"pagePath": "pages/vod/video",
|
||||||
"iconPath": "static/common/play.png",
|
"iconPath": "static/common/play.png",
|
||||||
"selectedIconPath": "static/common/playc.png",
|
"selectedIconPath": "static/common/playc.png",
|
||||||
"text": "点播"
|
"text": "点播"
|
||||||
}, */
|
},
|
||||||
{
|
{
|
||||||
"pagePath": "pages/my/my",
|
"pagePath": "pages/my/my",
|
||||||
"iconPath": "static/common/me.png",
|
"iconPath": "static/common/me.png",
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,60 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<uni-list>
|
||||||
|
<!-- 垂直排列,无略缩图,主标题+副标题显示 -->
|
||||||
|
<uni-list-item direction="column" :key="item.id" v-for="(item, index) in examList">
|
||||||
|
<template v-slot:header>
|
||||||
|
<view class="uni-title">{{item.name}}</view>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body>
|
||||||
|
<view class="uni-list-box">
|
||||||
|
<view class="uni-content">
|
||||||
|
<view class="uni-title-sub uni-ellipsis-2">开启时间: {{item.open_time}}至{{item.close_time}}</view>
|
||||||
|
<view class="uni-note">考试机会: {{item.chance}}次</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-slot:footer>
|
||||||
|
<view class="uni-footer">
|
||||||
|
<u-button size="mini" type="primary" @click="attendExam(item)">我要参加</u-button>
|
||||||
|
<u-button size="mini" type="info">成绩排名</u-button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</uni-list-item>
|
||||||
|
</uni-list>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
query:{
|
||||||
|
page: 1
|
||||||
|
},
|
||||||
|
examList: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.getExamList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getExamList(){
|
||||||
|
this.$u.api.getExamList(this.query).then(res=>{
|
||||||
|
this.examList = res.data.results
|
||||||
|
})
|
||||||
|
},
|
||||||
|
attendExam(val){
|
||||||
|
console.log(val)
|
||||||
|
uni.setStorageSync('currentExam', val)
|
||||||
|
uni.navigateTo({
|
||||||
|
url:"/pages/exam/preview"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<view class='wrap'>
|
||||||
|
<view class="subTitle">
|
||||||
|
考试信息
|
||||||
|
</view>
|
||||||
|
<view class="examContent">
|
||||||
|
<view>考试名称:<text>{{currentExam.name}}</text> </view>
|
||||||
|
<view>考试总分:<text>{{currentExam.paper_.total_score}}</text>分</view>
|
||||||
|
<view v-if="currentExam.limit>0">考试时长:<text>{{currentExam.limit}}</text>分钟</view>
|
||||||
|
<view v-else>考试时长:<text>不限时长</text></view>
|
||||||
|
<view>题目分布:单选<text>{{currentExam.paper_.danxuan_count}}</text>道;多选<text>{{currentExam.paper_.duoxuan_count}}</text>道;判断<text>{{currentExam.paper_.panduan_count}}</text>道</view>
|
||||||
|
<view>判分规则:单选{{currentExam.paper_.danxuan_score}}分,多选{{currentExam.paper_.duoxuan_score}}分,多选{{currentExam.paper_.panduan_score}}分,错选少选均不得分</view>
|
||||||
|
</view>
|
||||||
|
<view class="subTitle">答题须知</view>
|
||||||
|
<view class="tipsArea">
|
||||||
|
<ul>
|
||||||
|
<li>1.进入答题后请不要后退或返回。</li>
|
||||||
|
<li>2.可点击上一题/下一题切换。</li>
|
||||||
|
<li>3.可点击答题卡复查</li>
|
||||||
|
<li>4.请合理安排时间答题,可提前交卷</li>
|
||||||
|
</ul>
|
||||||
|
</view>
|
||||||
|
<u-button type="primary" @click="start()">开始答题</u-button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentExam:{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.currentExam = uni.getStorageSync('currentExam')
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
start(){
|
||||||
|
uni.reLaunch({
|
||||||
|
url:'/pages/exam/main'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wrap{
|
||||||
|
padding: 24rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
.subTitle {
|
||||||
|
font-size: $u-font-size-title;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $theme-color;
|
||||||
|
text-align: center;
|
||||||
|
margin: 40rpx auto 20rpx auto;
|
||||||
|
}
|
||||||
|
.examContent {
|
||||||
|
line-height: 50rpx;
|
||||||
|
margin-left: 80rpx;
|
||||||
|
text {
|
||||||
|
font-weight: bold;
|
||||||
|
color:$u-type-warning-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tipsArea {
|
||||||
|
padding: 0 40rpx;
|
||||||
|
margin-left: 0rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
margin: 0 30rpx;
|
||||||
|
line-height: 50rpx;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,133 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<uni-list>
|
||||||
|
<uni-list-item v-for="item in list" :key="item.id" @click="goDetail(item.id)" :clickable="true" link>
|
||||||
|
<!-- 自定义 header -->
|
||||||
|
<!-- <view slot="header" class="slot-box">
|
||||||
|
<view>{{item.name}}</view>
|
||||||
|
</view> -->
|
||||||
|
<!-- 自定义 body -->
|
||||||
|
<template slot="body" style="display: block;">
|
||||||
|
<view>
|
||||||
|
<text v-if="item.type=='自助模考'">模拟练习</text>
|
||||||
|
<text v-else style="font-weight: bold;color:orange">正式竞赛</text>
|
||||||
|
<text v-if="item.name.indexOf('(补)') != -1">(补)</text>
|
||||||
|
</view>
|
||||||
|
<view style="color:gray;font-size: 26rpx;">
|
||||||
|
<span>耗时:
|
||||||
|
<span style="color:darkblue;font-weight: bold;">{{item.took}}</span>
|
||||||
|
s</span>
|
||||||
|
-
|
||||||
|
<text>提交时间:{{item.create_time}}</text>
|
||||||
|
</view>
|
||||||
|
<view style="color:gray;font-size: 26rpx;">
|
||||||
|
<span>总分:{{item.total_score}}分</span>
|
||||||
|
-
|
||||||
|
<span>得分:
|
||||||
|
<span style="color:green;font-weight: bold;">{{item.score}}</span>
|
||||||
|
分</span>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<!-- 自定义 footer-->
|
||||||
|
<!-- <template slot="footer">
|
||||||
|
<image class="slot-image" src="/static/logo.png" mode="widthFix"></image>
|
||||||
|
</template> -->
|
||||||
|
|
||||||
|
</uni-list-item>
|
||||||
|
</uni-list>
|
||||||
|
<!-- <uni-card :title="item.name" :isFull="true" isShadow='true' extra="查看详情" @click="clickCard" v-for="item in list" :key="item.id">
|
||||||
|
<view>
|
||||||
|
<text>{{item.type}}</text>
|
||||||
|
</view>
|
||||||
|
<view>
|
||||||
|
<text>耗时:{{item.took}}</text>
|
||||||
|
<text>开始答题:{{item.start_time}}</text>
|
||||||
|
</view>
|
||||||
|
</uni-card> -->
|
||||||
|
<view style="color:gray;text-align: center;margin-top:20upx">{{loadingText}}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
listQuery: {
|
||||||
|
page: 1,
|
||||||
|
page_size: 20
|
||||||
|
},
|
||||||
|
list: [],
|
||||||
|
loadingText: '加载中...'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getList() {
|
||||||
|
var that = this
|
||||||
|
that.$u.api.getMyExamRecord(that.listQuery).then(res => {
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: res.data.count + '条答题记录'
|
||||||
|
})
|
||||||
|
if (that.listQuery.page == 1) {
|
||||||
|
if (res.data.results.length == 0) {
|
||||||
|
that.loadingText = '暂无答题记录'
|
||||||
|
} else {
|
||||||
|
that.loadingText = ''
|
||||||
|
that.list = res.data.results
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
that.loadingText = ''
|
||||||
|
that.list = that.list.concat(res.data.results)
|
||||||
|
}
|
||||||
|
}).catch(res => {
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
if (res.code == 404) {
|
||||||
|
that.loadingText = '到底了'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
goDetail(id) {
|
||||||
|
uni.showLoading({
|
||||||
|
title:"正在获取答题详情",
|
||||||
|
})
|
||||||
|
this.$u.api.getExamRecordDetail(id).then(res=>{
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.setStorageSync('currentExam', res.data)
|
||||||
|
if (res.data.questions.length>0){
|
||||||
|
uni.redirectTo({
|
||||||
|
url:'/pages/exam/detail?examrecord='+id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
uni.showToast({
|
||||||
|
title:'获取失败',
|
||||||
|
icon:'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}).catch(e=>{
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.listQuery.page = 1
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
this.listQuery.page = 1
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
|
onReachBottom() {
|
||||||
|
this.listQuery.page = this.listQuery.page + 1
|
||||||
|
this.getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss'>
|
||||||
|
page {
|
||||||
|
background-color: $u-bg-color;
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,97 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<image class="examImage" :src="imageSrc" mode="aspectFit"></image>
|
||||||
|
<view class="finishText">
|
||||||
|
恭喜您完成考试!
|
||||||
|
</view>
|
||||||
|
<view class="finishText">
|
||||||
|
<view><text>{{currentExam.name}}</text></view>
|
||||||
|
<!-- 练习类型{{paperRecord.paperName}} -->
|
||||||
|
<view>总分:<text>{{currentExam.total_score}}</text>分</view>
|
||||||
|
<!-- <view>答对:<text>{{paperRecord.rightQueNum}}</text>题</view> -->
|
||||||
|
<view>得分:<text>{{currentExam.score}}</text> 分 </view>
|
||||||
|
</view>
|
||||||
|
<view class="btnArea">
|
||||||
|
<u-button class="btnClass" type="primary" :ripple="true" shape="circle" @click="goDetail">查看答卷</u-button>
|
||||||
|
<u-button class="btnClass" :ripple="true" shape="circle" @click="backToHome">返回首页</u-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageSrc: '/static/exam/result.png',
|
||||||
|
currentExam:{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goDetail(){
|
||||||
|
uni.showLoading({
|
||||||
|
title:"正在获取答题详情",
|
||||||
|
})
|
||||||
|
this.$u.api.getExamRecordDetail(this.currentExam.id).then(res=>{
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.setStorageSync('currentExam', res.data)
|
||||||
|
uni.redirectTo({
|
||||||
|
url:'/pages/exam/detail?examrecord='+this.currentExam.id
|
||||||
|
})
|
||||||
|
}).catch(e=>{
|
||||||
|
})
|
||||||
|
},
|
||||||
|
backToHome(){
|
||||||
|
uni.reLaunch({
|
||||||
|
url:'/pages/index/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(options){
|
||||||
|
this.currentExam = uni.getStorageSync('currentExam')
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.examImage {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
// background-color: #000000;
|
||||||
|
margin-top: 100rpx;
|
||||||
|
height: 300rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finishText {
|
||||||
|
// margin: 100rpx 200rpx;
|
||||||
|
padding: 50rpx;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
font-size: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
text {
|
||||||
|
color: $theme-color;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnArea {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 50rpx;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
.btnClass {
|
||||||
|
width: 300rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoArea {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -31,7 +31,7 @@
|
||||||
</video>
|
</video>
|
||||||
<view style="color:darkblue;margin-left:4upx">
|
<view style="color:darkblue;margin-left:4upx">
|
||||||
<text>{{video.name}}</text>
|
<text>{{video.name}}</text>
|
||||||
<text style="float:right;color:gray;margin-right: 8upx;">{{video.viewsp}}人观看</text>
|
<text style="float:right;color:gray;margin-right: 8upx;">{{video.views}}次播放</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
|
// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
|
||||||
// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
|
// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
|
||||||
|
|
||||||
|
$theme-color:#0080d1;
|
||||||
$u-main-color: #303133;
|
$u-main-color: #303133;
|
||||||
$u-content-color: #606266;
|
$u-content-color: #606266;
|
||||||
$u-tips-color: #909399;
|
$u-tips-color: #909399;
|
||||||
|
@ -36,3 +37,9 @@ $u-type-info-light: #f4f4f5;
|
||||||
|
|
||||||
$u-form-item-height: 70rpx;
|
$u-form-item-height: 70rpx;
|
||||||
$u-form-item-border-color: #dcdfe6;
|
$u-form-item-border-color: #dcdfe6;
|
||||||
|
|
||||||
|
/* 文字尺寸 */
|
||||||
|
$u-font-size-sm:24rpx;
|
||||||
|
$u-font-size-lg:32rpx;
|
||||||
|
$u-font-size-base:28rpx;
|
||||||
|
$u-font-size-title:36rpx;
|
|
@ -19,6 +19,7 @@ from apps.supervision.models import Content, Record
|
||||||
from apps.supervision.serializers import ContentSerializer, RecordCreateSerializer, RecordSerializer
|
from apps.supervision.serializers import ContentSerializer, RecordCreateSerializer, RecordSerializer
|
||||||
from apps.system.mixins import CreateUpdateCustomMixin
|
from apps.system.mixins import CreateUpdateCustomMixin
|
||||||
from utils.queryset import get_child_queryset2
|
from utils.queryset import get_child_queryset2
|
||||||
|
from apps.system.permission_data import RbacFilterSet
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from apps.supervision.permission import RecordPermission
|
from apps.supervision.permission import RecordPermission
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
@ -60,7 +61,7 @@ class AbilityContentViewSet(CreateUpdateCustomMixin, ModelViewSet):
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
serializer.save(update_by = self.request.user)
|
serializer.save(update_by = self.request.user)
|
||||||
|
|
||||||
class AbilityRecordViewSet(PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
|
class AbilityRecordViewSet(RbacFilterSet, PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
|
||||||
perms_map = {'get': '*', 'post': '*',
|
perms_map = {'get': '*', 'post': '*',
|
||||||
'put': '*', 'delete': '*'}
|
'put': '*', 'delete': '*'}
|
||||||
queryset = Record.objects.filter(content__cate=2)
|
queryset = Record.objects.filter(content__cate=2)
|
||||||
|
@ -70,25 +71,6 @@ class AbilityRecordViewSet(PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
|
||||||
ordering = ['-task', 'content__sortnum', '-create_time']
|
ordering = ['-task', 'content__sortnum', '-create_time']
|
||||||
filterset_fields = ['content','content__cate', 'belong_dept', 'state']
|
filterset_fields = ['content','content__cate', 'belong_dept', 'state']
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = self.queryset
|
|
||||||
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
|
|
||||||
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
|
||||||
if self.request.user.is_superuser:
|
|
||||||
pass
|
|
||||||
if hasattr(queryset.model, 'belong_dept'):
|
|
||||||
user = self.request.user
|
|
||||||
roles = user.roles
|
|
||||||
data_range = roles.values_list('datas', flat=True)
|
|
||||||
if '全部' in data_range:
|
|
||||||
pass
|
|
||||||
elif '本级及以下' in data_range:
|
|
||||||
belong_depts = get_child_queryset2(user.dept)
|
|
||||||
queryset = queryset.filter(belong_dept__in = belong_depts)
|
|
||||||
elif '本级' in data_range:
|
|
||||||
queryset = queryset.filter(belong_dept = user.dept)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
if not self.request.query_params.get('pageoff', None):
|
if not self.request.query_params.get('pageoff', None):
|
||||||
queryset = queryset.exclude(state='待发布')
|
queryset = queryset.exclude(state='待发布')
|
||||||
|
|
|
@ -13,6 +13,7 @@ from rest_framework.exceptions import ParseError
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from utils.queryset import get_child_queryset2
|
from utils.queryset import get_child_queryset2
|
||||||
|
from apps.system.permission_data import RbacFilterSet
|
||||||
|
|
||||||
|
|
||||||
class QualiLibViewSet(PageOrNot, ListModelMixin, GenericViewSet):
|
class QualiLibViewSet(PageOrNot, ListModelMixin, GenericViewSet):
|
||||||
|
@ -112,7 +113,7 @@ def cal_count(qtask, org):
|
||||||
qorg.save()
|
qorg.save()
|
||||||
|
|
||||||
|
|
||||||
class QActionViewSet(PageOrNot, ListModelMixin, DestroyModelMixin, RetrieveModelMixin, GenericViewSet):
|
class QActionViewSet(RbacFilterSet, PageOrNot, ListModelMixin, DestroyModelMixin, RetrieveModelMixin, GenericViewSet):
|
||||||
perms_map = {'get': '*', 'delete': 'qaction_delete'}
|
perms_map = {'get': '*', 'delete': 'qaction_delete'}
|
||||||
queryset = QAction.objects.select_related(
|
queryset = QAction.objects.select_related(
|
||||||
'file', 'atype', 'afield', 'qtask', 'belong_dept', 'create_by')
|
'file', 'atype', 'afield', 'qtask', 'belong_dept', 'create_by')
|
||||||
|
@ -126,24 +127,7 @@ class QActionViewSet(PageOrNot, ListModelMixin, DestroyModelMixin, RetrieveModel
|
||||||
return QActionDetailSerializer
|
return QActionDetailSerializer
|
||||||
return super().get_serializer_class()
|
return super().get_serializer_class()
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = self.queryset
|
|
||||||
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
|
|
||||||
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
|
||||||
if self.request.user.is_superuser:
|
|
||||||
pass
|
|
||||||
if hasattr(queryset.model, 'belong_dept'):
|
|
||||||
user = self.request.user
|
|
||||||
roles = user.roles
|
|
||||||
data_range = roles.values_list('datas', flat=True)
|
|
||||||
if '全部' in data_range:
|
|
||||||
pass
|
|
||||||
elif '本级及以下' in data_range:
|
|
||||||
belong_depts = get_child_queryset2(user.dept)
|
|
||||||
queryset = queryset.filter(belong_dept__in=belong_depts)
|
|
||||||
elif '本级' in data_range:
|
|
||||||
queryset = queryset.filter(belong_dept=user.dept)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
# @action(methods=['get'], detail=False, perms_map={'get': '*'})
|
# @action(methods=['get'], detail=False, perms_map={'get': '*'})
|
||||||
# def my(self, request, *args, **kwargs):
|
# def my(self, request, *args, **kwargs):
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
from .models import ExamRecord, Exam
|
||||||
|
class ExamRecordFilter(filters.FilterSet):
|
||||||
|
class Meta:
|
||||||
|
model = ExamRecord
|
||||||
|
fields = {
|
||||||
|
'start_time': ['exact', 'gte', 'lte'],
|
||||||
|
'is_pass': ['exact'],
|
||||||
|
'type': ['exact'],
|
||||||
|
'is_submited': ['exact'],
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExamFilter(filters.FilterSet):
|
||||||
|
class Meta:
|
||||||
|
model = Exam
|
||||||
|
fields = {
|
||||||
|
'close_time': ['exact', 'gte', 'lte'],
|
||||||
|
'paper': ['exact'],
|
||||||
|
'code': ['exact']
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
# Generated by Django 3.0.5 on 2022-11-07 05:56
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('exam', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AnswerDetail',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
|
||||||
|
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
|
||||||
|
('user_answer', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)),
|
||||||
|
('score', models.FloatField(default=0, verbose_name='本题得分')),
|
||||||
|
('is_right', models.BooleanField(default=False, verbose_name='是否正确')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '答题记录',
|
||||||
|
'verbose_name_plural': '答题记录',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Exam',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
|
||||||
|
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
|
||||||
|
('code', models.CharField(blank=True, max_length=100, null=True, unique=True, verbose_name='考试编号')),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='名称')),
|
||||||
|
('place', models.CharField(blank=True, max_length=100, null=True, verbose_name='考试地点')),
|
||||||
|
('open_time', models.DateTimeField(blank=True, null=True, verbose_name='开启时间')),
|
||||||
|
('close_time', models.DateTimeField(blank=True, null=True, verbose_name='关闭时间')),
|
||||||
|
('proctor_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='监考人姓名')),
|
||||||
|
('proctor_phone', models.CharField(blank=True, max_length=100, null=True, verbose_name='监考人联系方式')),
|
||||||
|
('chance', models.IntegerField(default=3, verbose_name='考试机会')),
|
||||||
|
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='exam_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Paper',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
|
||||||
|
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
|
||||||
|
('name', models.CharField(max_length=200, verbose_name='名称')),
|
||||||
|
('limit', models.IntegerField(default=0, verbose_name='限时(分钟)')),
|
||||||
|
('total_score', models.FloatField(default=0, verbose_name='满分')),
|
||||||
|
('pass_score', models.FloatField(default=0, verbose_name='通过分数')),
|
||||||
|
('danxuan_count', models.IntegerField(default=0, verbose_name='单选数量')),
|
||||||
|
('danxuan_score', models.FloatField(default=2, verbose_name='单选分数')),
|
||||||
|
('duoxuan_count', models.IntegerField(default=0, verbose_name='多选数量')),
|
||||||
|
('duoxuan_score', models.FloatField(default=4, verbose_name='多选分数')),
|
||||||
|
('panduan_count', models.IntegerField(default=0, verbose_name='判断数量')),
|
||||||
|
('panduan_score', models.FloatField(default=2, verbose_name='判断分数')),
|
||||||
|
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paper_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '押题卷',
|
||||||
|
'verbose_name_plural': '押题卷',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaperQuestion',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
|
||||||
|
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
|
||||||
|
('total_score', models.FloatField(default=0, verbose_name='单题满分')),
|
||||||
|
('sort', models.PositiveSmallIntegerField(default=1)),
|
||||||
|
('paper', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exam.Paper', verbose_name='试卷')),
|
||||||
|
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exam.Question', verbose_name='试题')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='paper',
|
||||||
|
name='questions',
|
||||||
|
field=models.ManyToManyField(through='exam.PaperQuestion', to='exam.Question'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='paper',
|
||||||
|
name='update_by',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paper_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExamRecord',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
|
||||||
|
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
|
||||||
|
('name', models.CharField(max_length=200, verbose_name='名称')),
|
||||||
|
('type', models.CharField(choices=[('自助模考', '自助模考'), ('押卷模考', '押卷模考'), ('正式考试', '正式考试')], default='自助模考', max_length=50, verbose_name='考试类型')),
|
||||||
|
('limit', models.IntegerField(default=0, verbose_name='限时(分钟)')),
|
||||||
|
('total_score', models.FloatField(default=0, verbose_name='总分')),
|
||||||
|
('score', models.FloatField(default=0, verbose_name='得分')),
|
||||||
|
('took', models.IntegerField(default=0, verbose_name='耗时(秒)')),
|
||||||
|
('start_time', models.DateTimeField(verbose_name='开始答题时间')),
|
||||||
|
('end_time', models.DateTimeField(verbose_name='结束答题时间')),
|
||||||
|
('is_pass', models.BooleanField(default=True, verbose_name='是否通过')),
|
||||||
|
('questions', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, verbose_name='下发的题目列表')),
|
||||||
|
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='examrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||||
|
('detail', models.ManyToManyField(through='exam.AnswerDetail', to='exam.Question', verbose_name='答题记录')),
|
||||||
|
('exam', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='exam.Exam', verbose_name='关联的正式考试')),
|
||||||
|
('paper', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='exam.Paper', verbose_name='所用试卷')),
|
||||||
|
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='examrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '考试记录',
|
||||||
|
'verbose_name_plural': '考试记录',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='paper',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='exam.Paper', verbose_name='使用的试卷'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='update_by',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='exam_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='answerdetail',
|
||||||
|
name='examrecord',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exam.ExamRecord'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='answerdetail',
|
||||||
|
name='question',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exam.Question'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 3.0.5 on 2022-11-08 01:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('exam', '0002_auto_20221107_1356'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='examrecord',
|
||||||
|
name='questions',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='paperquestion',
|
||||||
|
name='sort',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='answerdetail',
|
||||||
|
name='total_score',
|
||||||
|
field=models.FloatField(default=0, verbose_name='该题满分'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='examrecord',
|
||||||
|
name='is_submited',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from apps.system.models import CommonAModel
|
from apps.system.models import CommonAModel
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
|
from utils.model import BaseModel
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
class Questioncat(CommonAModel):
|
class Questioncat(CommonAModel):
|
||||||
|
@ -44,4 +45,90 @@ class Question(CommonAModel):
|
||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Paper(CommonAModel):
|
||||||
|
name = models.CharField(max_length=200, verbose_name='名称')
|
||||||
|
questions = models.ManyToManyField(Question, through='PaperQuestion')
|
||||||
|
limit = models.IntegerField(default=0, verbose_name='限时(分钟)')
|
||||||
|
total_score = models.FloatField(default=0, verbose_name='满分')
|
||||||
|
pass_score = models.FloatField(default=0, verbose_name='通过分数')
|
||||||
|
danxuan_count = models.IntegerField(default=0, verbose_name='单选数量')
|
||||||
|
danxuan_score = models.FloatField(default=2, verbose_name='单选分数')
|
||||||
|
duoxuan_count = models.IntegerField(default=0, verbose_name='多选数量')
|
||||||
|
duoxuan_score = models.FloatField(default=4, verbose_name='多选分数')
|
||||||
|
panduan_count = models.IntegerField(default=0, verbose_name='判断数量')
|
||||||
|
panduan_score = models.FloatField(default=2, verbose_name='判断分数')
|
||||||
|
class Meta:
|
||||||
|
verbose_name = '押题卷'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class PaperQuestion(BaseModel):
|
||||||
|
paper = models.ForeignKey(Paper, on_delete=models.CASCADE, verbose_name='试卷')
|
||||||
|
question = models.ForeignKey(Question, on_delete=models.CASCADE, verbose_name='试题')
|
||||||
|
total_score = models.FloatField(default=0, verbose_name='单题满分')
|
||||||
|
|
||||||
|
|
||||||
|
class Exam(CommonAModel):
|
||||||
|
"""
|
||||||
|
组织的正式考试
|
||||||
|
"""
|
||||||
|
code = models.CharField('考试编号', max_length=100, null=True, blank=True, unique=True)
|
||||||
|
name = models.CharField('名称', max_length=100)
|
||||||
|
place = models.CharField('考试地点', max_length=100, null=True, blank=True)
|
||||||
|
open_time = models.DateTimeField('开启时间', null=True, blank=True)
|
||||||
|
close_time = models.DateTimeField('关闭时间', null=True, blank=True)
|
||||||
|
proctor_name = models.CharField('监考人姓名', max_length=100, null=True, blank=True)
|
||||||
|
proctor_phone = models.CharField('监考人联系方式', max_length=100, null=True, blank=True)
|
||||||
|
chance = models.IntegerField('考试机会', default=3)
|
||||||
|
paper = models.ForeignKey(Paper, verbose_name='使用的试卷', on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class ExamRecord(CommonAModel):
|
||||||
|
'''
|
||||||
|
考试记录表
|
||||||
|
'''
|
||||||
|
type_choices = (
|
||||||
|
('自助模考', '自助模考'),
|
||||||
|
('押卷模考', '押卷模考'),
|
||||||
|
('正式考试', '正式考试')
|
||||||
|
)
|
||||||
|
name = models.CharField(max_length=200, verbose_name='名称')
|
||||||
|
type = models.CharField(max_length=50, default='自助模考',choices = type_choices, verbose_name='考试类型')
|
||||||
|
limit = models.IntegerField(default=0, verbose_name='限时(分钟)')
|
||||||
|
paper = models.ForeignKey(Paper, on_delete=models.SET_NULL, verbose_name='所用试卷', null=True, blank=True)
|
||||||
|
total_score = models.FloatField(default=0, verbose_name='总分')
|
||||||
|
score = models.FloatField(default=0, verbose_name='得分')
|
||||||
|
took = models.IntegerField(default=0, verbose_name='耗时(秒)')
|
||||||
|
start_time = models.DateTimeField(verbose_name='开始答题时间')
|
||||||
|
end_time = models.DateTimeField(verbose_name='结束答题时间')
|
||||||
|
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, on_delete= models.SET_NULL)
|
||||||
|
is_submited = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = '考试记录'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerDetail(BaseModel):
|
||||||
|
examrecord = models.ForeignKey(ExamRecord, on_delete=models.CASCADE)
|
||||||
|
total_score = models.FloatField(default=0, verbose_name='该题满分')
|
||||||
|
question = models.ForeignKey(Question, on_delete=models.CASCADE)
|
||||||
|
user_answer = JSONField(null=True,blank=True)
|
||||||
|
score = models.FloatField(default=0, verbose_name='本题得分')
|
||||||
|
is_right = models.BooleanField(default=False, verbose_name='是否正确')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = '答题记录'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer, CharField, Serializer, SerializerMethodField, FloatField
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from apps.exam.models import Question, Questioncat
|
from apps.exam.models import Question, Questioncat, Paper, Exam, PaperQuestion, ExamRecord, AnswerDetail
|
||||||
|
|
||||||
|
|
||||||
class QuestioncatSerializer(ModelSerializer):
|
class QuestioncatSerializer(ModelSerializer):
|
||||||
|
@ -10,6 +11,174 @@ class QuestioncatSerializer(ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class QuestionSerializer(ModelSerializer):
|
class QuestionSerializer(ModelSerializer):
|
||||||
|
questioncat_name = serializers.CharField(source='questioncat.name', read_only=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Question
|
model = Question
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PaperSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Paper
|
||||||
|
exclude = ('questions',)
|
||||||
|
|
||||||
|
|
||||||
|
class PaperQuestionSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = PaperQuestion
|
||||||
|
fields = ['question', 'total_score']
|
||||||
|
|
||||||
|
|
||||||
|
class PaperCreateUpdateSerializer(ModelSerializer):
|
||||||
|
questions_ = PaperQuestionSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Paper
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PaperQuestionDetailSerializer(ModelSerializer):
|
||||||
|
name = serializers.ReadOnlyField(source='question.name')
|
||||||
|
options = serializers.ReadOnlyField(source='question.options')
|
||||||
|
right = serializers.ReadOnlyField(source='question.right')
|
||||||
|
type = serializers.ReadOnlyField(source='question.type')
|
||||||
|
img = serializers.ReadOnlyField(source='question.img')
|
||||||
|
questioncat_name = serializers.ReadOnlyField(
|
||||||
|
source='question.questioncat.name')
|
||||||
|
level = serializers.ReadOnlyField(source='question.level')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PaperQuestion
|
||||||
|
fields = ('id', 'name', 'options', 'right', 'type', 'level',
|
||||||
|
'total_score', 'questioncat_name', 'img', 'question')
|
||||||
|
|
||||||
|
|
||||||
|
class PaperQuestionShortSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = PaperQuestion
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PaperDetailSerializer(ModelSerializer):
|
||||||
|
questions_ = SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Paper
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_questions_(self, instance):
|
||||||
|
pqs = PaperQuestion.objects.filter(paper=instance)
|
||||||
|
return PaperQuestionDetailSerializer(pqs, many=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class ExamCreateUpdateSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Exam
|
||||||
|
fields = ['name', 'place', 'open_time',
|
||||||
|
'close_time', 'proctor_name', 'proctor_phone', 'chance', 'paper']
|
||||||
|
|
||||||
|
|
||||||
|
class ExamListSerializer(ModelSerializer):
|
||||||
|
create_by_name = CharField(source='create_by.name', read_only=True)
|
||||||
|
paper_ = PaperSerializer(source='paper', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Exam
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ExamDetailSerializer(ModelSerializer):
|
||||||
|
create_by_name = CharField(source='create_by.name', read_only=True)
|
||||||
|
paper_ = PaperSerializer(source='paper', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Exam
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ExamAttendSerializer(Serializer):
|
||||||
|
code = CharField(label="考试编号")
|
||||||
|
|
||||||
|
|
||||||
|
class ExamRecordListSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
考试列表序列化
|
||||||
|
"""
|
||||||
|
took_format = serializers.SerializerMethodField()
|
||||||
|
create_by_name = serializers.CharField(
|
||||||
|
source='create_by.name', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ExamRecord
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class ExamRecordDetailSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
考试详情序列化
|
||||||
|
"""
|
||||||
|
took_format = serializers.SerializerMethodField()
|
||||||
|
questions_ = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ExamRecord
|
||||||
|
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_questions_(self, obj):
|
||||||
|
objs = AnswerDetail.objects.select_related('question').filter(
|
||||||
|
examrecord=obj).order_by('question__type')
|
||||||
|
return AnswerDetailSerializer(instance=objs, many=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerDetailUpdateSerializer(serializers.Serializer):
|
||||||
|
id = serializers.CharField(label='下发ID')
|
||||||
|
user_answer = serializers.JSONField(label='作答')
|
||||||
|
|
||||||
|
|
||||||
|
class ExamRecordSubmitSerializer(serializers.ModelSerializer):
|
||||||
|
questions_ = AnswerDetailUpdateSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ExamRecord
|
||||||
|
fields = ['questions_']
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerDetailSerializer(ModelSerializer):
|
||||||
|
name = serializers.ReadOnlyField(source='question.name')
|
||||||
|
options = serializers.ReadOnlyField(source='question.options')
|
||||||
|
right = serializers.ReadOnlyField(source='question.right')
|
||||||
|
type = serializers.ReadOnlyField(source='question.type')
|
||||||
|
img = serializers.ReadOnlyField(source='question.img')
|
||||||
|
questioncat_name = serializers.ReadOnlyField(
|
||||||
|
source='question.questioncat.name')
|
||||||
|
level = serializers.ReadOnlyField(source='question.level')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PaperQuestion
|
||||||
|
fields = ['id', 'question', 'name', 'options', 'right', 'type', 'level',
|
||||||
|
'total_score', 'questioncat_name', 'img', 'user_answer', 'score', 'is_right']
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerDetailOutSerializer(ModelSerializer):
|
||||||
|
name = serializers.ReadOnlyField(source='question.name')
|
||||||
|
options = serializers.ReadOnlyField(source='question.options')
|
||||||
|
type = serializers.ReadOnlyField(source='question.type')
|
||||||
|
img = serializers.ReadOnlyField(source='question.img')
|
||||||
|
questioncat_name = serializers.ReadOnlyField(
|
||||||
|
source='question.questioncat.name')
|
||||||
|
level = serializers.ReadOnlyField(source='question.level')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PaperQuestion
|
||||||
|
fields = ['id', 'question', 'name', 'options', 'type', 'level',
|
||||||
|
'total_score', 'questioncat_name', 'img', 'user_answer', 'score', 'is_right']
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from apps.exam.views import QuestionViewSet, QuestioncatViewSet
|
from apps.exam.views import QuestionViewSet, QuestioncatViewSet, PaperViewSet, ExamViewSet, ExamRecordViewSet
|
||||||
|
|
||||||
API_BASE_URL = 'api/exam/'
|
API_BASE_URL = 'api/exam/'
|
||||||
HTML_BASE_URL = 'exam/'
|
HTML_BASE_URL = 'exam/'
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register('questioncat', QuestioncatViewSet, basename='questioncat')
|
router.register('questioncat', QuestioncatViewSet, basename='questioncat')
|
||||||
router.register('question', QuestionViewSet, basename="question")
|
router.register('question', QuestionViewSet, basename="question")
|
||||||
|
router.register('paper', PaperViewSet, basename="paper")
|
||||||
|
router.register('exam', ExamViewSet, basename="exam")
|
||||||
|
router.register('examrecord', ExamRecordViewSet, basename="examrecord")
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(API_BASE_URL, include(router.urls))
|
path(API_BASE_URL, include(router.urls))
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,50 +1,95 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet, GenericViewSet
|
||||||
|
from rest_framework.mixins import ListModelMixin, DestroyModelMixin
|
||||||
from apps.exam.exports import export_question
|
from apps.exam.exports import export_question
|
||||||
from apps.exam.models import Question, Questioncat
|
from apps.exam.models import Question, Questioncat, PaperQuestion
|
||||||
from apps.exam.serializers import QuestionSerializer, QuestioncatSerializer
|
from apps.exam.serializers import (QuestionSerializer, QuestioncatSerializer, PaperSerializer, ExamDetailSerializer, ExamRecordDetailSerializer, ExamListSerializer,
|
||||||
|
ExamCreateUpdateSerializer, ExamListSerializer, ExamRecordSubmitSerializer, PaperDetailSerializer, PaperCreateUpdateSerializer, AnswerDetailOutSerializer, ExamRecordListSerializer)
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.exceptions import ParseError
|
||||||
from openpyxl import Workbook, load_workbook
|
from openpyxl import Workbook, load_workbook
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from apps.exam.models import Paper, Exam, ExamRecord, AnswerDetail
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db import transaction
|
||||||
|
from rest_framework.serializers import Serializer
|
||||||
|
from datetime import datetime
|
||||||
|
from apps.exam.filters import ExamRecordFilter, ExamFilter
|
||||||
|
from datetime import timedelta
|
||||||
|
from apps.system.mixins import CreateUpdateCustomMixin
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
|
|
||||||
class QuestioncatViewSet(ModelViewSet):
|
def enctry(s):
|
||||||
perms_map = {'*': '*'}
|
k = 'ez9z3a4m*$%srn9ve_t71yd!v+&xn9@0k(e(+l6#g1h=e5i4da'
|
||||||
|
encry_str = ""
|
||||||
|
for i, j in zip(s, k):
|
||||||
|
# i为字符,j为秘钥字符
|
||||||
|
temp = str(ord(i)+ord(j))+'_' # 加密字符 = 字符的Unicode码 + 秘钥的Unicode码
|
||||||
|
encry_str = encry_str + temp
|
||||||
|
return encry_str
|
||||||
|
|
||||||
|
|
||||||
|
# 解密
|
||||||
|
def dectry(p):
|
||||||
|
k = 'ez9z3a4m*$%srn9ve_t71yd!v+&xn9@0k(e(+l6#g1h=e5i4da'
|
||||||
|
dec_str = ""
|
||||||
|
for i, j in zip(p.split("_")[:-1], k):
|
||||||
|
# i 为加密字符,j为秘钥字符
|
||||||
|
# 解密字符 = (加密Unicode码字符 - 秘钥字符的Unicode码)的单字节字符
|
||||||
|
temp = chr(int(i) - ord(j))
|
||||||
|
dec_str = dec_str+temp
|
||||||
|
return dec_str
|
||||||
|
|
||||||
|
|
||||||
|
class QuestioncatViewSet(CreateUpdateCustomMixin, ModelViewSet):
|
||||||
|
perms_map = {'get': '*', 'post':'question', 'put':'question', 'delete':'question'}
|
||||||
queryset = Questioncat.objects.all()
|
queryset = Questioncat.objects.all()
|
||||||
serializer_class = QuestioncatSerializer
|
serializer_class = QuestioncatSerializer
|
||||||
filterset_fields = ['parent']
|
filterset_fields = ['parent']
|
||||||
search_fields = ['name']
|
search_fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
class QuestionViewSet(ModelViewSet):
|
class QuestionViewSet(CreateUpdateCustomMixin, ModelViewSet):
|
||||||
perms_map = {'*': '*'}
|
perms_map = {'get': '*', 'post':'question', 'put':'question', 'delete':'question'}
|
||||||
queryset = Question.objects.all()
|
queryset = Question.objects.all()
|
||||||
serializer_class = QuestionSerializer
|
serializer_class = QuestionSerializer
|
||||||
filterset_fields = ['level', 'type', 'year']
|
filterset_fields = ['level', 'type', 'year']
|
||||||
search_fields = ['name', 'options', 'resolution']
|
search_fields = ['name', 'options', 'resolution']
|
||||||
|
|
||||||
@action(methods=['get'], detail=False,
|
@action(methods=['get'], detail=False,
|
||||||
url_path='export', url_name='export_question', perms_map=[{'get': '*'}])
|
url_path='export', url_name='export_question', perms_map=[{'get': '*'}], serializer_class=Serializer)
|
||||||
def export_question(self, request):
|
def export_question(self, request):
|
||||||
|
"""
|
||||||
|
导出题目
|
||||||
|
|
||||||
|
导出题目
|
||||||
|
"""
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
path = export_question(queryset)
|
path = export_question(queryset)
|
||||||
return Response({'path': path})
|
return Response({'path': path})
|
||||||
|
|
||||||
@action(methods=['post'], detail=False, url_name='enable_question', permission_classes=[IsAuthenticated])
|
@action(methods=['post'], detail=False, url_name='enable_question', perms_map={'post': 'question'}, serializer_class=Serializer)
|
||||||
def enable(self, request):
|
def enable(self, request):
|
||||||
|
"""
|
||||||
|
启用题目
|
||||||
|
|
||||||
|
启用题目
|
||||||
|
"""
|
||||||
ids = request.data.get('ids', None)
|
ids = request.data.get('ids', None)
|
||||||
if ids:
|
if ids:
|
||||||
Question.objects.filter(pk__in=ids).update(enabled=True)
|
Question.objects.filter(pk__in=ids).update(enabled=True)
|
||||||
return Response(status=200)
|
return Response(status=200)
|
||||||
|
|
||||||
@action(methods=['post'], detail=False,
|
@action(methods=['post'], detail=False,
|
||||||
url_path='import', url_name='import_question', perms_map=[{'post': '*'}])
|
url_path='import', url_name='import_question', perms_map={'post': 'question'}, serializer_class=Serializer)
|
||||||
def import_question(self, request):
|
def import_question(self, request):
|
||||||
"""
|
"""
|
||||||
|
导入题目
|
||||||
|
|
||||||
导入题目
|
导入题目
|
||||||
"""
|
"""
|
||||||
xlsxpath = request.data['path']
|
xlsxpath = request.data['path']
|
||||||
|
@ -167,3 +212,207 @@ class QuestionViewSet(ModelViewSet):
|
||||||
obj.save()
|
obj.save()
|
||||||
i = i + 1
|
i = i + 1
|
||||||
return Response(notinlist, status=200)
|
return Response(notinlist, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
class PaperViewSet(ModelViewSet):
|
||||||
|
"""
|
||||||
|
试卷增删改查
|
||||||
|
"""
|
||||||
|
perms_map = {'get': '*', 'post':'paper', 'put':'paper', 'delete':'paper'}
|
||||||
|
queryset = Paper.objects.all()
|
||||||
|
serializer_class = PaperSerializer
|
||||||
|
ordering = ['id']
|
||||||
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action in ['retrieve']:
|
||||||
|
return PaperDetailSerializer
|
||||||
|
elif self.action in ['create', 'update']:
|
||||||
|
return PaperCreateUpdateSerializer
|
||||||
|
return super().get_serializer_class()
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
sr = PaperCreateUpdateSerializer(data=request.data)
|
||||||
|
sr.is_valid(raise_exception=True)
|
||||||
|
vdata = sr.validated_data
|
||||||
|
vdata['create_by'] = request.user
|
||||||
|
questions_ = vdata.pop('questions_')
|
||||||
|
paper = Paper.objects.create(**vdata)
|
||||||
|
q_list = []
|
||||||
|
for i in questions_:
|
||||||
|
q_list.append(PaperQuestion(question=i['question'], total_score=i['total_score'], paper=paper))
|
||||||
|
PaperQuestion.objects.bulk_create(q_list)
|
||||||
|
return Response(status=201)
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
# 有考试在执行,不可更新
|
||||||
|
now = timezone.now()
|
||||||
|
paper = self.get_object()
|
||||||
|
if Exam.objects.filter(close_time__gte=now, paper=paper).exists():
|
||||||
|
raise ParseError('存在考试,不可编辑')
|
||||||
|
sr = PaperCreateUpdateSerializer(instance=paper, data=request.data)
|
||||||
|
sr.is_valid(raise_exception=True)
|
||||||
|
vdata = sr.validated_data
|
||||||
|
questions_ = vdata.pop('questions_')
|
||||||
|
vdata['update_by'] = request.user
|
||||||
|
Paper.objects.filter(id=paper.id).update(**vdata)
|
||||||
|
q_list = []
|
||||||
|
for i in questions_:
|
||||||
|
q_list.append(PaperQuestion(question=i['question'], total_score=i['total_score'], paper=paper))
|
||||||
|
PaperQuestion.objects.filter(paper=paper).delete()
|
||||||
|
PaperQuestion.objects.bulk_create(q_list)
|
||||||
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
|
class ExamViewSet(CreateUpdateCustomMixin, ModelViewSet):
|
||||||
|
perms_map = {'get': '*', 'post':'exam', 'put':'exam', 'delete':'exam'}
|
||||||
|
queryset = Exam.objects.all().select_related('paper', 'create_by')
|
||||||
|
ordering = ['-id']
|
||||||
|
search_fields = ('name',)
|
||||||
|
serializer_class = ExamListSerializer
|
||||||
|
filterset_class = ExamFilter
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action in ['create', 'update']:
|
||||||
|
return ExamCreateUpdateSerializer
|
||||||
|
elif self.action in ['retrieve']:
|
||||||
|
return ExamDetailSerializer
|
||||||
|
return super().get_serializer_class()
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
if ExamRecord.objects.filter(exam=instance).exists():
|
||||||
|
raise ParseError('存在考试记录,禁止删除')
|
||||||
|
instance.delete(soft=False)
|
||||||
|
return Response(status=204)
|
||||||
|
|
||||||
|
@action(methods=['post'], detail=True, perms_map=[{'post': '*'}], serializer_class=Serializer, permission_classes = [IsAuthenticated])
|
||||||
|
def start(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
开始考试
|
||||||
|
|
||||||
|
开始考试具体题目信息
|
||||||
|
"""
|
||||||
|
exam = self.get_object()
|
||||||
|
now = timezone.now()
|
||||||
|
if now < exam.open_time or now > exam.close_time:
|
||||||
|
raise ParseError('不在考试时间范围')
|
||||||
|
tests = ExamRecord.objects.filter(
|
||||||
|
exam=exam, create_by=request.user)
|
||||||
|
chance_used = tests.count()
|
||||||
|
if chance_used > exam.chance:
|
||||||
|
raise ParseError('考试机会已用完')
|
||||||
|
if exam.paper:
|
||||||
|
er = ExamRecord()
|
||||||
|
er.type = '正式考试'
|
||||||
|
er.name = '正式考试' + now.strftime('%Y%m%d%H%M')
|
||||||
|
er.limit = exam.paper.limit
|
||||||
|
er.paper = exam.paper
|
||||||
|
er.total_score = exam.paper.total_score
|
||||||
|
er.start_time = now
|
||||||
|
er.is_pass = False
|
||||||
|
er.exam = exam
|
||||||
|
er.save()
|
||||||
|
ret = {}
|
||||||
|
ret['examrecord'] = er.id
|
||||||
|
pqs = PaperQuestion.objects.filter(paper=exam.paper).order_by('id')
|
||||||
|
details = []
|
||||||
|
for i in pqs:
|
||||||
|
details.append(AnswerDetail(examrecord=er, question=i.question, total_score=i.total_score))
|
||||||
|
AnswerDetail.objects.bulk_create(details)
|
||||||
|
ads = AnswerDetail.objects.select_related('question').filter(examrecord=er).order_by('id')
|
||||||
|
ret['questions_'] = AnswerDetailOutSerializer(instance=ads, many=True).data
|
||||||
|
return Response(ret)
|
||||||
|
raise ParseError('暂不支持')
|
||||||
|
|
||||||
|
|
||||||
|
class ExamRecordViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
|
||||||
|
"""
|
||||||
|
考试记录列表和详情
|
||||||
|
"""
|
||||||
|
perms_map = {'get': 'examrecord', 'post': '*', 'delete':'examrecord'}
|
||||||
|
queryset = ExamRecord.objects.select_related('create_by')
|
||||||
|
serializer_class = ExamRecordListSerializer
|
||||||
|
ordering_fields = ['create_time', 'score', 'took', 'update_time']
|
||||||
|
ordering = ['-update_time']
|
||||||
|
search_fields = ('create_by__name', 'create_by__username')
|
||||||
|
filterset_class = ExamRecordFilter
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action == 'retrieve':
|
||||||
|
return ExamRecordDetailSerializer
|
||||||
|
return super().get_serializer_class()
|
||||||
|
|
||||||
|
def perform_destroy(self, instance): # 考试记录物理删除
|
||||||
|
instance.delete(soft=False)
|
||||||
|
|
||||||
|
@action(methods=['post'], detail=False, perms_map=[{'post': '*'}], serializer_class=Serializer, permission_classes = [IsAuthenticated])
|
||||||
|
def clear(self, request, pk=None):
|
||||||
|
"""
|
||||||
|
清除七日前未提交的考试记录
|
||||||
|
|
||||||
|
清除七日前未提交的考试记录
|
||||||
|
"""
|
||||||
|
now = timezone.now
|
||||||
|
days7_ago = now - timedelta(days=7)
|
||||||
|
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=['post'], detail=True, perms_map=[{'post': '*'}], serializer_class=ExamRecordSubmitSerializer, permission_classes = [IsAuthenticated])
|
||||||
|
def submit(self, request, pk=None):
|
||||||
|
'''
|
||||||
|
提交答卷
|
||||||
|
|
||||||
|
提交答卷
|
||||||
|
'''
|
||||||
|
er = self.get_object()
|
||||||
|
now = timezone.now()
|
||||||
|
if er.create_by != request.user:
|
||||||
|
raise ParseError('提交人有误')
|
||||||
|
exam = er.exam
|
||||||
|
if not exam:
|
||||||
|
raise ParseError('暂不支持')
|
||||||
|
if now > exam.close_time + timedelta(minutes=30):
|
||||||
|
raise ParseError('考试时间已过, 提交失败')
|
||||||
|
serializer = ExamRecordSubmitSerializer(data = request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
vdata = serializer.validated_data
|
||||||
|
questions_ = vdata['questions_']
|
||||||
|
# 后端判卷
|
||||||
|
ads = AnswerDetail.objects.select_related('question').filter(examrecord=er).order_by('id').values('id', 'question__right', 'total_score', 'question__type')
|
||||||
|
total_score = 0
|
||||||
|
try:
|
||||||
|
for index, ad in enumerate(ads):
|
||||||
|
ad.user_answer = questions_[index]['user_answer']
|
||||||
|
if ad.question__type == '多选':
|
||||||
|
if set(ad.question__right) == set(ad.user_answer):
|
||||||
|
ad.is_right = True
|
||||||
|
ad.score = ad.total_score
|
||||||
|
else:
|
||||||
|
if ad.question__right == ad.user_answer:
|
||||||
|
ad.is_right = True
|
||||||
|
ad.score = ad.total_score
|
||||||
|
ad.save()
|
||||||
|
total_score = total_score + ad.score
|
||||||
|
except Exception as e:
|
||||||
|
raise ParseError('判卷失败, 请检查试卷:' + str(e))
|
||||||
|
er.score = total_score
|
||||||
|
if er.score > 0.6*er.total_score:
|
||||||
|
er.is_pass = True
|
||||||
|
er.took = (now - er.create_time).total_seconds()
|
||||||
|
er.end_time = now
|
||||||
|
er.is_submited = True
|
||||||
|
er.save()
|
||||||
|
return Response()
|
|
@ -98,7 +98,7 @@ class SubtaskViewSet(PageOrNot, CreateUpdateCustomMixin, OptimizationMixin, Mode
|
||||||
if has_permission('inspecttask_create', self.request.user):
|
if has_permission('inspecttask_create', self.request.user):
|
||||||
return queryset
|
return queryset
|
||||||
else:
|
else:
|
||||||
return queryset.filter(team_subtask__member=self.request.user).exclude(state='待发布')
|
return queryset.filter(team_subtask__member__id=self.request.user.id).exclude(state='待发布')
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, perms_map = {'get':'*'})
|
@action(methods=['get'], detail=False, perms_map = {'get':'*'})
|
||||||
def self(self, request, *args, **kwargs):
|
def self(self, request, *args, **kwargs):
|
||||||
|
|
|
@ -209,7 +209,7 @@ from utils.queryset import get_child_queryset2
|
||||||
|
|
||||||
from .permission import RecordPermission
|
from .permission import RecordPermission
|
||||||
|
|
||||||
class RecordViewSet(PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
|
class RecordViewSet(RbacFilterSet, PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
|
||||||
perms_map = {'get': '*', 'post': 'record_create',
|
perms_map = {'get': '*', 'post': 'record_create',
|
||||||
'put': 'record_update', 'delete': 'record_delete'}
|
'put': 'record_update', 'delete': 'record_delete'}
|
||||||
queryset = Record.objects.filter(content__cate=1)
|
queryset = Record.objects.filter(content__cate=1)
|
||||||
|
@ -219,25 +219,6 @@ class RecordViewSet(PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
|
||||||
ordering = ['-task', 'content__sortnum', '-create_time']
|
ordering = ['-task', 'content__sortnum', '-create_time']
|
||||||
filter_class = RecordFilter # 过滤类
|
filter_class = RecordFilter # 过滤类
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = self.queryset
|
|
||||||
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
|
|
||||||
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
|
||||||
if self.request.user.is_superuser:
|
|
||||||
pass
|
|
||||||
if hasattr(queryset.model, 'belong_dept'):
|
|
||||||
user = self.request.user
|
|
||||||
roles = user.roles
|
|
||||||
data_range = roles.values_list('datas', flat=True)
|
|
||||||
if '全部' in data_range:
|
|
||||||
pass
|
|
||||||
elif '本级及以下' in data_range:
|
|
||||||
belong_depts = get_child_queryset2(user.dept)
|
|
||||||
queryset = queryset.filter(belong_dept__in = belong_depts)
|
|
||||||
elif '本级' in data_range:
|
|
||||||
queryset = queryset.filter(belong_dept = user.dept)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
if not self.request.query_params.get('pageoff', None):
|
if not self.request.query_params.get('pageoff', None):
|
||||||
queryset = queryset.exclude(state='待发布')
|
queryset = queryset.exclude(state='待发布')
|
||||||
|
|
|
@ -12,7 +12,7 @@ def get_permission_list(user):
|
||||||
perms_list = ['admin']
|
perms_list = ['admin']
|
||||||
else:
|
else:
|
||||||
perms = Permission.objects.none()
|
perms = Permission.objects.none()
|
||||||
roles = user.roles.all()
|
roles = user.roles.all() if hasattr(user, 'roles') else None
|
||||||
if roles:
|
if roles:
|
||||||
for i in roles:
|
for i in roles:
|
||||||
perms = perms | i.perms.all()
|
perms = perms | i.perms.all()
|
||||||
|
|
|
@ -34,7 +34,9 @@ class RbacFilterSet(object):
|
||||||
|
|
||||||
if hasattr(queryset.model, 'belong_dept'):
|
if hasattr(queryset.model, 'belong_dept'):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
roles = user.roles
|
roles = user.roles if hasattr(user, 'roles') else []
|
||||||
|
if not roles:
|
||||||
|
return queryset.none()
|
||||||
data_range = roles.values_list('datas', flat=True)
|
data_range = roles.values_list('datas', flat=True)
|
||||||
if '全部' in data_range:
|
if '全部' in data_range:
|
||||||
return queryset
|
return queryset
|
||||||
|
@ -57,6 +59,8 @@ class RbacFilterSet(object):
|
||||||
elif '仅本人' in data_range:
|
elif '仅本人' in data_range:
|
||||||
queryset = queryset.filter(Q(create_by=user)|Q(update_by=user))
|
queryset = queryset.filter(Q(create_by=user)|Q(update_by=user))
|
||||||
return queryset
|
return queryset
|
||||||
|
else:
|
||||||
|
return queryset.none()
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ class MyViewRecordAPIView(APIView):
|
||||||
video.save()
|
video.save()
|
||||||
if request.data.get('current', None):
|
if request.data.get('current', None):
|
||||||
record.current = request.data.get('current')
|
record.current = request.data.get('current')
|
||||||
if timezone.now() > record.update_time + timedelta(hours=6):
|
if timezone.now() > record.update_time + timedelta(minutes=30):
|
||||||
record.views = record.views + 1
|
record.views = record.views + 1
|
||||||
video.views = video.views + 1
|
video.views = video.views + 1
|
||||||
video.save()
|
video.save()
|
||||||
|
|
Loading…
Reference in New Issue