This commit is contained in:
caoqianming 2021-03-22 11:38:22 +08:00
commit ee1b614411
76 changed files with 1366 additions and 222 deletions

View File

@ -0,0 +1,29 @@
import request from '@/utils/request'
export function getexamlist(query) {
return request({
url: '/examtest/exam/',
method: 'get',
params: query
})
}
export function createexam(data) {
return request({
url: '/examtest/exam/',
method: 'post',
data
})
}
export function updateexam(id, data) {
return request({
url: `/examtest/exam/${id}/`,
method: 'put',
data
})
}
export function deleteexam(id) {
return request({
url: `/examtest/exam/${id}/`,
method: 'delete',
})
}

View File

@ -35,10 +35,11 @@ export function deleteTestRule(id) {
})
}
export function getWorkScopeAll() {
export function getWorkScopeAll(query) {
return request({
url: '/examtest/workscope/',
method: 'get',
params: query
})
}
@ -131,4 +132,19 @@ export function exportTest(query) {
method: 'get',
params: query
})
}
export function exportwTest(id,data) {
return request({
url: `/examtest/examtest/${id}/exportw/`,
method: 'post',
data: data
})
}
export function deleteExamtest(id) {
return request({
url: `/examtest/examtest/${id}/`,
method: 'delete',
})
}

View File

@ -247,6 +247,21 @@ export const asyncRoutes = [
},
]
},
{
path: '/exammanage',
component: Layout,
redirect: '/exammanage/index',
name: 'Exammanage',
meta: { title: '考试管理', icon: '', perms: []},
children: [
{
path: 'index',
name: 'exam',
component: () => import('@/views/exam/index.vue'),
meta: { title: '考试管理', icon: 'component', perms: ['exam_view'] }
},
]
},
{
path: '/analyse',
component: Layout,
@ -268,6 +283,7 @@ export const asyncRoutes = [
},
]
},
{
path: '/qtest',
component: Layout,

View File

@ -64,9 +64,61 @@ div:focus {
padding: 10px;
}
.el-table--medium td,   .el-table--medium th {
padding: 2px 0;
}
.el-form-item {
margin-bottom: 16px;
}
.el-card, .el-message {
border-radius: 0px;
overflow: hidden;
}
.el-card__body {
padding: 6px;
}
.el-card__header {
padding: 6px;
}
}
.el-tabs--border-card>.el-tabs__content {
padding: 6px;
}
.el-dialog__header {
padding: 10px 10px 6px;
}
// .el-dialog{
// display: flex;
// flex-direction: column;
// margin:0 !important;
// position:absolute;
// top:50%;
// left:50%;
// transform:translate(-50%,-50%);
// /*height:600px;*/
// max-height:calc(100% - 30px);
// max-width:calc(100% - 30px);
// }
.el-dialog .el-dialog__body{
// flex:1;
// overflow: auto;
padding: 8px 12px;
}
.el-form--label-top .el-form-item__label {
line-height: 16px;
}
.el-button+.el-button {
margin-left: 1px;
}
.el-tabs__header {
margin: 0 0 6px;
}
.pagination-container {
padding: 0px 0px;
}
body .el-table th.gutter{
display: table-cell!important;
}
.el-dialog__footer{
padding: 6px 6px 6px;
}

View File

@ -18,7 +18,7 @@
</el-select>
<el-select
v-model="listQuery.type"
placeholder="考类型"
placeholder="类型"
clearable
style="width: 200px"
class="filter-item"
@ -54,7 +54,7 @@
icon="el-icon-refresh-left"
@click="resetFilter"
>刷新重置</el-button>
<el-button type="primary" icon="el-icon-download" @click="exportTest">导出Excel</el-button>
<el-button type="primary" icon="el-icon-download" @click="exportTest" >导出Excel</el-button>
<div style="margin-top:10px">
</div>
</div>
@ -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,26 @@
<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>
<el-button
v-if="scope.row.type=='正式考试'"
type="danger"
size="small"
@click="handleDelete(scope)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@ -116,7 +131,7 @@
</template>
<script>
import { getExamTestlist, exportTest } from "@/api/examtest";
import { getExamTestlist, exportTest, exportwTest, deleteExamtest } from "@/api/examtest";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination";
@ -138,6 +153,7 @@ export default {
typeOptions: [
{ key: "自助模考", label: "自助模考", value: "自助模考" },
{ key: "押卷模考", label: "押卷模考", value: "押卷模考"},
{ key: "正式考试", label: "正式考试", value: "正式考试"},
],
passOptions: [
{ key: true, label: "通过", value: true },
@ -186,10 +202,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 +235,37 @@ 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()
window.open(res.data.path, "_blank");
}).catch(e=>{loading.close()})
},
handleDelete(scope){
this.$confirm("确认删除该考试记录吗?将丢失数据!", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error"
})
.then(async () => {
await deleteExamtest(scope.row.id);
this.getList()
this.$message({
type: "success",
message: "成功删除!"
});
})
.catch(err => {
console.error(err);
});
},
handleExport2(scope) {
const loading = this.$loading({text: '正在重新生成word...',});
exportwTest(scope.row.id, {anew:true}).then(res=>{
loading.close()
window.open(res.data.path, "_blank");
}).catch(e=>{loading.close()})
},
exportTest() {
const loading = this.$loading();
@ -235,7 +288,8 @@ export default {
this.getList()
}
},
}
};
</script>

View File

@ -212,12 +212,7 @@ export default {
updateCompany(this.company.id, this.company).then(() => {
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "编辑成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
} else {
createCompany(this.company).then(res => {
@ -225,12 +220,7 @@ export default {
// this.tableData.unshift(this.company)
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "新增成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
}
} else {

View File

@ -156,11 +156,7 @@ export default {
const { description, name } = this.role
this.dialogVisible = false
this.$notify({
title: '成功',
dangerouslyUseHTMLString: true,
type: 'success'
})
this.$message.success('成功')
},
}
}

View File

@ -0,0 +1,274 @@
<template>
<div class="app-container">
<div style="margin-top:10px">
<el-input
v-model="listQuery.search"
placeholder="输入考试名称进行搜索"
style="width: 300px;"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button type="primary" @click="handleAdd" icon="el-icon-plus" v-if ="checkPermission(['exam_create'])">新增</el-button>
<el-button
class="filter-item"
type="primary"
icon="el-icon-refresh-left"
@click="resetFilter"
>刷新重置</el-button>
</div>
<el-table
:data="tableData.results"
style="width: 100%;margin-top:10px;"
border
fit
v-loading="listLoading"
highlight-current-row
max-height="600"
row-key="id"
default-expand-all
>
<el-table-column label="考试名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="考试编号">
<template slot-scope="scope">{{ scope.row.code }}</template>
</el-table-column>
<el-table-column label="考试地点">
<template slot-scope="scope">{{ scope.row.place }}</template>
</el-table-column>
<el-table-column label="工作类别">
<template slot-scope="scope">{{ scope.row.workscope }}</template>
</el-table-column>
<el-table-column label="开启时间">
<template slot-scope="scope">{{ scope.row.opentime }}</template>
</el-table-column>
<el-table-column label="关闭时间">
<template slot-scope="scope">{{ scope.row.closetime }}</template>
</el-table-column>
<el-table-column label="创建人">
<template slot-scope="scope">
<span>{{ scope.row.create_admin_name }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作" fixed="right">
<template slot-scope="scope">
<el-button
type="primary"
size="small"
@click="handleView(scope)"
:disabled="!checkPermission(['exam_view'])"
>详情</el-button>
<el-button
size="small"
@click="handleEdit(scope)"
:disabled="!checkPermission(['exam_update'])"
>编辑</el-button>
<el-button
type="danger"
size="small"
@click="handleDelete(scope)"
:disabled="!checkPermission(['exam_delete'])"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.count>0"
:total="tableData.count"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
<el-dialog :visible.sync="dialogVisible" :title="dialogType==='edit'?'编辑考试':'新增考试'" >
<el-form :model="exam" label-width="80px" :rules="rule1" ref="examForm">
<el-form-item label="名称" prop="name">
<el-input v-model="exam.name" placeholder="名称" />
</el-form-item>
<el-form-item label="考试地点" prop="place">
<el-input v-model="exam.place" placeholder="考试地点" />
</el-form-item>
<el-form-item label="工作类别" prop="workscope" >
<el-select v-model="exam.workscope" placeholder="请选择工作类别" style="width:100%">
<el-option
v-for="item in workscopeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="开启时间" prop="opentime">
<el-date-picker
v-model="exam.opentime"
type="datetime"
placeholder="开启时间"
style="width:100%">
</el-date-picker>
</el-form-item>
<el-form-item label="关闭时间" prop="closetime">
<el-date-picker
v-model="exam.closetime"
type="datetime"
placeholder="关闭时间"
style="width:100%"
></el-date-picker>
</el-form-item>
<el-form-item label="监考人姓名" prop="proctor_name" label-width="120px">
<el-input v-model="exam.proctor_name" placeholder="监考人姓名" />
</el-form-item>
<el-form-item label="监考人联系方式" prop="proctor_phone" label-width="120px">
<el-input v-model="exam.proctor_phone" placeholder="监考人联系方式" />
</el-form-item>
</el-form>
<div style="text-align:right;">
<el-button type="danger" @click="dialogVisible=false">取消</el-button>
<el-button type="primary" @click="confirmexam('examForm')">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getexamlist, createexam, deleteexam, updateexam} from "@/api/exam";
import { getWorkScopeAll } from "@/api/examtest"
import { deepClone } from "@/utils";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination"
const defaultexam = {
id: "",
name: "",
place: "",
opentime: null,
closetime: null,
};
const listQuery = {
page: 1,
limit: 20,
search: ""
}
export default {
components: { Pagination },
data() {
return {
selects:[],
exam: {
id: "",
name: "",
},
listQuery:listQuery,
tableData: {count:0},
listLoading: true,
dialogVisible: false,
dialogType: "new",
workscopeOptions:[],
rule1: {
name: [{ required: true, message: "请输入", trigger: "blur" }],
place: [{ required: true, message: "请输入", trigger: "change" }],
workscope: [{ required: true, message: "请选择", trigger: "change" }],
opentime: [{ required: true, message: "请选择", trigger: "change" }],
closetime: [{ required: true, message: "请选择", trigger: "change" }],
proctor_name: [{ required: true, message: "请输入", trigger: "change" }],
proctor_phone: [{ required: true, message: "请输入", trigger: "change" }]
},
};
},
computed: {},
created() {
this.getList();
this.getworkscopeOptions();
},
methods: {
checkPermission,
getworkscopeOptions(){
getWorkScopeAll({can_exam:true}).then(res=>{
this.workscopeOptions = res.data
})
},
getList() {
this.listLoading = true
getexamlist(this.listQuery).then(response => {
this.tableData = response.data
this.listLoading = false
});
},
resetFilter() {
this.listQuery = {
page: 1,
limit: 20,
search: ""
};
this.getList();
},
handleFilter() {
this.listQuery.page = 1;
this.getList();
},
handleAdd() {
this.exam = Object.assign({}, defaultexam);
this.dialogType = "new";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["examForm"].clearValidate();
});
},
handleEdit(scope) {
this.exam = Object.assign({}, scope.row); // copy obj
this.dialogType = "edit";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["examForm"].clearValidate();
});
},
handleDelete(scope) {
this.$confirm("确认删除该考试吗?将丢失数据!", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error"
})
.then(async () => {
await deleteexam(scope.row.id);
this.getList()
this.$message({
type: "success",
message: "成功删除!"
});
})
.catch(err => {
// console.error(err);
});
},
handleView(scope){
this.$router.push({name:'ExamTest', params:{exam:scope.row.id}})
},
async confirmexam(form) {
this.$refs[form].validate(valid => {
if (valid) {
const isEdit = this.dialogType === "edit";
if (isEdit) {
updateexam(this.exam.id, this.exam).then(() => {
this.getList();
this.dialogVisible = false;
this.$message.success('成功')
});
} else {
createexam(this.exam).then(res => {
// this.exam = res.data
// this.tableData.unshift(this.exam)
this.getList();
this.dialogVisible = false;
this.$message.success('成功')
});
}
} else {
return false;
}
});
},
}
};
</script>

View File

@ -20,14 +20,20 @@
row-key="id"
>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column align="center" label="名称">
<el-table-column label="名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column align="center" label="所属学科">
<el-table-column label="所属学科">
<template slot-scope="scope">
<el-tag type="success" >{{scope.row.subject_name}}</el-tag>
</template>
</el-table-column>
<el-table-column label="是否可组织考试">
<template slot-scope="scope">
<el-tag v-if="scope.row.can_exam"></el-tag>
<el-tag type="warning" v-else></el-tag>
</template>
</el-table-column>
<el-table-column label="创建日期">
<template slot-scope="scope">
<span>{{ scope.row.create_time }}</span>

View File

@ -10,6 +10,9 @@
<el-form-item label="工作类别" prop="name">
<el-input v-model="Form.name" style="width:400px"></el-input>
</el-form-item>
<el-form-item label="是否可组织考试" prop="can_exam" label-width="120px">
<el-switch v-model="Form.can_exam"></el-switch>
</el-form-item>
<el-form-item label="所属学科" prop="subject">
<el-select v-model="Form.subject" placeholder="请选择所属学科" style="width:400px">
<el-option
@ -57,6 +60,7 @@ export default {
return {
Form: {
name: "",
can_exam:false,
subject: null,
questioncat: [],
},

View File

@ -10,6 +10,9 @@
<el-form-item label="工作类别" prop="name">
<el-input v-model="Form.name" style="width:400px"></el-input>
</el-form-item>
<el-form-item label="是否可组织考试" prop="can_exam" label-width="120px">
<el-switch v-model="Form.can_exam"></el-switch>
</el-form-item>
<el-form-item label="所属学科" prop="subject">
<el-select v-model="Form.subject" placeholder="请选择所属学科" style="width:400px">
<el-option
@ -57,6 +60,7 @@ export default {
return {
Form: {
name: "",
can_exam:false,
subject: null,
questioncat: [],
},

View File

@ -43,10 +43,13 @@
<span v-else>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column align="header-center" label="类型">
<el-table-column label="类型">
<template slot-scope="scope">{{ scope.row.type }}</template>
</el-table-column>
<el-table-column align="header-center" label="上传时间">
<el-table-column align="header-center" label="排序码">
<template slot-scope="scope">{{ scope.row.sort }}</template>
</el-table-column>
<el-table-column label="上传时间">
<template slot-scope="scope">{{ scope.row.create_time }}</template>
</el-table-column>
<el-table-column align="header-center" label="下载量/播放量">
@ -96,6 +99,9 @@
<el-form-item label="描述" prop="description">
<el-input v-model="material.description" placeholder="描述" />
</el-form-item>
<el-form-item label="排序" prop="description">
<el-input-number v-model="material.sort" :min="1"></el-input-number>
</el-form-item>
<!-- <el-form-item label="封面" prop="poster" v-if="material.type=='视频'">
<el-upload
accept=".png, .jpeg, .jpg"
@ -334,12 +340,7 @@ export default {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "编辑成功",
type: "success",
duration: 2000,
});
this.$message.success('成功')
}
});
} else {
@ -347,10 +348,7 @@ export default {
.then((res) => {
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
type: "success",
});
this.$message.success('成功')
})
.catch((error) => {});
}

View File

@ -229,12 +229,7 @@ export default {
() => {
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "编辑成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
}
);
} else {
@ -243,12 +238,7 @@ export default {
// this.tableData.unshift(this.questioncat)
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "新增成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
}
} else {

View File

@ -155,12 +155,7 @@ export default {
() => {
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "编辑成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
}
);
} else {
@ -169,12 +164,7 @@ export default {
// this.tableData.unshift(this.subject)
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "新增成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
}
} else {

View File

@ -201,12 +201,7 @@ export default {
updateBanner(this.banner.id, this.banner).then(() => {
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "编辑成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
} else {
createBanner(this.banner).then(res => {
@ -214,12 +209,7 @@ export default {
// this.tableData.unshift(this.banner)
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "新增成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
}
} else {

View File

@ -180,12 +180,7 @@ export default {
updateOrg(this.org.id, this.org).then(() => {
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "编辑成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
} else {
this.org.pid = this.org.pid.pop()
@ -194,12 +189,7 @@ export default {
// this.tableData.unshift(this.org)
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "新增成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
}
} else {

View File

@ -377,12 +377,7 @@ export default {
}
}
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "编辑成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
} else {
this.user.department = this.user.department.pop();
@ -391,12 +386,7 @@ export default {
// this.userList.unshift(this.user)
this.getList();
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "新增成功",
type: "success",
duration: 2000
});
this.$message.success('成功')
});
}
} else {

View File

@ -29,7 +29,9 @@
"pages/candidate/detail",
"pages/material/video",
"pages/qtest/form",
"pages/main/start"
"pages/main/start",
"pages/exam/index",
"pages/exam/note"
],
"window": {
"backgroundTextStyle": "light",

View File

@ -0,0 +1,8 @@
"use strict";function e(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}/*!
* mp-html v2.0.5
* https://github.com/jin-yufeng/mp-html
*
* Released under the MIT license
* Author: Jin Yufeng
*/
var t=require("./parser"),n=[];Component({data:{nodes:[]},properties:{content:{type:String,value:"",observer:function(e){this.setContent(e)}},copyLink:{type:Boolean,value:!0},domain:String,errorImg:String,lazyLoad:Boolean,loadingImg:String,pauseVideo:{type:Boolean,value:!0},previewImg:{type:Boolean,value:!0},scrollTable:Boolean,selectable:null,setTitle:{type:Boolean,value:!0},showImgMenu:{type:Boolean,value:!0},tagStyle:Object,useAnchor:null},created:function(){this.plugins=[];for(var e=n.length;e--;)this.plugins.push(new n[e](this))},detached:function(){clearInterval(this._timer),this._hook("onDetached")},methods:{in:function(e,t,n){e&&t&&n&&(this._in={page:e,selector:t,scrollTop:n})},navigateTo:function(t,n){var o=this;return new Promise(function(i,r){if(!o.data.useAnchor)return r("Anchor is disabled");var a=wx.createSelectorQuery().in(o._in?o._in.page:o).select((o._in?o._in.selector:"._root")+(t?"".concat(">>>","#").concat(t):"")).boundingClientRect();o._in?a.select(o._in.selector).scrollOffset().select(o._in.selector).boundingClientRect():a.selectViewport().scrollOffset(),a.exec(function(t){if(!t[0])return r("Label not found");var a=t[1].scrollTop+t[0].top-(t[2]?t[2].top:0)+(n||parseInt(o.data.useAnchor)||0);o._in?o._in.page.setData(e({},o._in.scrollTop,a)):wx.pageScrollTo({scrollTop:a,duration:300}),i()})})},getText:function(e){var t="";return function e(n){for(var o=0;o<n.length;o++){var i=n[o];if("text"==i.type)t+=i.text.replace(/&amp;/g,"&");else if("br"==i.name)t+="\n";else{var r="p"==i.name||"div"==i.name||"tr"==i.name||"li"==i.name||"h"==i.name[0]&&i.name[1]>"0"&&i.name[1]<"7";r&&t&&"\n"!=t[t.length-1]&&(t+="\n"),i.children&&e(i.children),r&&"\n"!=t[t.length-1]?t+="\n":"td"!=i.name&&"th"!=i.name||(t+="\t")}}}(e||this.data.nodes),t},getRect:function(){var e=this;return new Promise(function(t,n){wx.createSelectorQuery().in(e).select("._root").boundingClientRect().exec(function(e){return e[0]?t(e[0]):n("Root label not found")})})},setContent:function(e,n){var o=this;this.imgList&&n||(this.imgList=[]),this._videos=[];var i={},r=new t(this).parse(e);if(n)for(var a=this.data.nodes.length,l=r.length;l--;)i["nodes[".concat(a+l,"]")]=r[l];else i.nodes=r;this.setData(i,function(){o._hook("onLoad"),o.triggerEvent("load")});var s;clearInterval(this._timer),this._timer=setInterval(function(){o.getRect().then(function(e){e.height==s&&(o.triggerEvent("ready",e),clearInterval(o._timer)),s=e.height}).catch(function(){})},350)},_hook:function(e){for(var t=n.length;t--;)this.plugins[t][e]&&this.plugins[t][e]()},_add:function(e){e.detail.root=this}}});

View File

@ -0,0 +1 @@
{"component":true,"usingComponents":{"node":"./node/node"}}

View File

@ -0,0 +1 @@
<view class="_root {{selectable?'_select':''}}"><slot wx:if="{{!nodes[0]}}"/><node id="_root" childs="{{nodes}}" opts="{{[lazyLoad,loadingImg,errorImg,showImgMenu]}}" catchadd="_add"/></view>

View File

@ -0,0 +1 @@
._root{padding:1px 0;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}._select{-webkit-user-select:text;user-select:text}

View File

@ -0,0 +1 @@
"use strict";function t(t,e,i){return e in t?Object.defineProperty(t,e,{value:i,enumerable:!0,configurable:!0,writable:!0}):t[e]=i,t}Component({data:{ctrl:{}},properties:{childs:Array,opts:Array},attached:function(){this.triggerEvent("add",this,{bubbles:!0,composed:!0})},methods:{noop:function(){},getNode:function(t){for(var e=t.split("_"),i=this.data.childs[e[0]],r=1;r<e.length;r++)i=i.children[e[r]];return i},play:function(t){if(this.root.data.pauseVideo){for(var e=!1,i=t.target.id,r=this.root._videos.length;r--;)this.root._videos[r].id==i?e=!0:this.root._videos[r].pause();if(!e){var s=wx.createVideoContext(i,this);s.id=i,this.root._videos.push(s)}}},imgTap:function(t){var e=this.getNode(t.target.dataset.i);if(e.a)return this.linkTap(e.a);if(!e.attrs.ignore&&(this.root.triggerEvent("imgtap",e.attrs),this.root.data.previewImg)){var i=this.root.imgList[e.i];wx.previewImage({current:i,urls:this.root.imgList})}},imgLoad:function(e){var i,r=e.target.dataset.i,s=this.getNode(r);s.w?(this.data.opts[1]&&!this.data.ctrl[r]||-1==this.data.ctrl[r])&&(i=1):i=e.detail.width,i&&this.setData(t({},"ctrl."+r,i))},linkTap:function(t){var e=t.currentTarget?this.getNode(t.currentTarget.dataset.i):{},i=e.attrs||t,r=i.href;this.root.triggerEvent("linktap",Object.assign({innerText:this.root.getText(e.children||[])},i)),r&&("#"==r[0]?this.root.navigateTo(r.substring(1)).catch(function(){}):r.includes("://")?this.root.data.copyLink&&wx.setClipboardData({data:r,success:function(){return wx.showToast({title:"链接已复制"})}}):wx.navigateTo({url:r,fail:function(){wx.switchTab({url:r,fail:function(){}})}}))},mediaError:function(e){var i=e.target.dataset.i,r=this.getNode(i);if("video"==r.name||"audio"==r.name){var s=(this.data.ctrl[i]||0)+1;if(s>r.src.length&&(s=0),s<r.src.length)return this.setData(t({},"ctrl."+i,s))}else"img"==r.name&&this.data.opts[2]&&this.setData(t({},"ctrl."+i,-1));this.root&&this.root.triggerEvent("error",{source:r.name,attrs:r.attrs,errMsg:e.detail.errMsg})}}});

View File

@ -0,0 +1 @@
{"component":true,"usingComponents":{"node":"./node"}}

View File

@ -0,0 +1 @@
<wxs module="use">var e={abbr:!0,b:!0,big:!0,code:!0,del:!0,em:!0,i:!0,ins:!0,label:!0,q:!0,small:!0,span:!0,strong:!0,sub:!0,sup:!0};module.exports=function(n){return!(e[n.name]||n.children&&-1!=(n.attrs.style||"").indexOf("inline"))&&!n.c};</wxs><template name="el"><block wx:if="{{n.name=='img'}}"><image wx:if="{{(opts[1]&&!ctrl[i])||ctrl[i]<0}}" class="_img" style="{{n.attrs.style}}" src="{{ctrl[i]<0?opts[2]:opts[1]}}" mode="widthFix"/><image id="{{n.attrs.id}}" class="_img {{n.attrs.class}}" style="{{ctrl[i]==-1?'display:none;':''}}width:{{ctrl[i]||1}}px;height:1px;{{n.attrs.style}}" src="{{n.attrs.src}}" mode="{{n.h?'':'widthFix'}}" lazy-load="{{opts[0]}}" webp="{{n.webp}}" show-menu-by-longpress="{{opts[3]&&!n.attrs.ignore}}" data-i="{{i}}" bindload="imgLoad" binderror="mediaError" catchtap="imgTap" bindlongpress="noop"/></block><text wx:elif="{{n.type=='text'}}" user-select="{{n.us}}" decode>{{n.text}}</text><text wx:elif="{{n.name=='br'}}">\n</text><view wx:elif="{{n.name=='a'}}" id="{{n.attrs.id}}" class="{{n.attrs.href?'_a ':''}}{{n.attrs.class}}" hover-class="_hover" style="display:inline;{{n.attrs.style}}" data-i="{{i}}" catchtap="linkTap"><node childs="{{n.children}}" opts="{{opts}}" style="display:inherit"/></view><video wx:elif="{{n.name=='video'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" autoplay="{{n.attrs.autoplay}}" controls="{{n.attrs.controls}}" loop="{{n.attrs.loop}}" muted="{{n.attrs.muted}}" poster="{{n.attrs.poster}}" src="{{n.src[ctrl[i]||0]}}" data-i="{{i}}" bindplay="play" binderror="mediaError"/><audio wx:elif="{{n.name=='audio'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" author="{{n.attrs.author}}" controls="{{n.attrs.controls}}" loop="{{n.attrs.loop}}" name="{{n.attrs.name}}" poster="{{n.attrs.poster}}" src="{{n.src[ctrl[i]||0]}}" data-i="{{i}}" bindplay="play" binderror="mediaError"/><rich-text wx:else id="{{n.attrs.id}}" style="{{n.f}}" nodes="{{[n]}}"/></template><block wx:for="{{childs}}" wx:for-item="n1" wx:for-index="i1" wx:key="i1"><template wx:if="{{use(n1)}}" is="el" data="{{n:n1,i:''+i1,opts:opts,ctrl:ctrl}}"/><view wx:else id="{{n1.attrs.id}}" class="_{{n1.name}} {{n1.attrs.class}}" style="{{n1.attrs.style}}"><block wx:for="{{n1.children}}" wx:for-item="n2" wx:for-index="i2" wx:key="i2"><template wx:if="{{use(n2)}}" is="el" data="{{n:n2,i:i1+'_'+i2,opts:opts,ctrl:ctrl}}"/><view wx:else id="{{n2.attrs.id}}" class="_{{n2.name}} {{n2.attrs.class}}" style="{{n2.attrs.style}}"><block wx:for="{{n2.children}}" wx:for-item="n3" wx:for-index="i3" wx:key="i3"><template wx:if="{{use(n3)}}" is="el" data="{{n:n3,i:i1+'_'+i2+'_'+i3,opts:opts,ctrl:ctrl}}"/><view wx:else id="{{n3.attrs.id}}" class="_{{n3.name}} {{n3.attrs.class}}" style="{{n3.attrs.style}}"><block wx:for="{{n3.children}}" wx:for-item="n4" wx:for-index="i4" wx:key="i4"><template wx:if="{{use(n4)}}" is="el" data="{{n:n4,i:i1+'_'+i2+'_'+i3+'_'+i4,opts:opts,ctrl:ctrl}}"/><view wx:else id="{{n4.attrs.id}}" class="_{{n4.name}} {{n4.attrs.class}}" style="{{n4.attrs.style}}"><block wx:for="{{n4.children}}" wx:for-item="n5" wx:for-index="i5" wx:key="i5"><template wx:if="{{use(n5)}}" is="el" data="{{n:n5,i:i1+'_'+i2+'_'+i3+'_'+i4+'_'+i5,opts:opts,ctrl:ctrl}}"/><node wx:else id="{{n5.attrs.id}}" class="_{{n5.name}} {{n5.attrs.class}}" style="{{n5.attrs.style}}" childs="{{n5.children}}" opts="{{opts}}"/></block></view></block></view></block></view></block></view></block>

View File

@ -0,0 +1 @@
._a{padding:1.5px 0 1.5px 0;color:#366092;word-break:break-all}._hover{text-decoration:underline;opacity:.7}._img{max-width:100%;-webkit-touch-callout:none}._b,._strong{font-weight:700}._code{font-family:monospace}._del{text-decoration:line-through}._em,._i{font-style:italic}._h1{font-size:2em}._h2{font-size:1.5em}._h3{font-size:1.17em}._h5{font-size:.83em}._h6{font-size:.67em}._h1,._h2,._h3,._h4,._h5,._h6{display:block;font-weight:700}._ins{text-decoration:underline}._li{display:list-item}._ol{list-style-type:decimal}._ol,._ul{display:block;padding-left:40px;margin:1em 0}._q::before{content:'"'}._q::after{content:'"'}._sub{font-size:smaller;vertical-align:sub}._sup{font-size:smaller;vertical-align:super}._tbody,._tfoot,._thead{display:table-row-group}._tr{display:table-row}._td,._th{display:table-cell;vertical-align:middle}._th{font-weight:700;text-align:center}._ul{list-style-type:disc}._ul ._ul{margin:0;list-style-type:circle}._ul ._ul ._ul{list-style-type:square}._abbr,._b,._code,._del,._em,._i,._ins,._label,._q,._span,._strong,._sub,._sup{display:inline}._blockquote,._div,._p{display:block}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615988351221" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4995" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M504.5248 189.7472c-43.7248-25.1904-99.4304-13.1072-128.8192 27.9552l-15.36 21.6064c-31.8464 44.544-72.6016 82.0224-119.808 109.8752l-106.1888 62.7712C97.792 433.5616 79.36 476.16 88.4736 517.5296l106.496 482.304 159.5392-276.2752-5.9392-5.0176c-51.9168-46.7968-66.7648-123.2896-31.6416-184.1152 40.6528-70.3488 132.7104-93.2864 205.6192-51.0976 73.0112 42.0864 99.2256 133.2224 58.5728 203.6736l-4.096 6.5536c-37.888 58.2656-112.64 80.4864-178.4832 55.5008L240.2304 1022.7712 709.632 876.032c40.5504-12.6976 68.3008-49.8688 68.8128-92.3648l1.4336-121.2416c0.7168-54.784 12.9024-108.8512 35.7376-158.6176l11.5712-25.088c21.0944-45.9776 3.7888-100.4544-40.0384-125.8496l-282.624-163.1232zM586.752 10.6496C569.856 0.9216 549.7856-1.7408 530.944 3.2768c-18.8416 5.0176-34.9184 17.408-44.6464 34.304l-2.7648 5.3248c-16.384 34.6112-3.4816 75.9808 29.696 95.1296l312.9344 180.6336c16.896 9.728 36.9664 12.3904 55.808 7.3728 18.8416-5.0176 34.9184-17.408 44.6464-34.304l2.8672-5.3248c16.384-34.6112 3.3792-76.0832-29.7984-95.1296L586.752 10.6496z m0 0" p-id="4996" fill="#d81e06"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,5 +1,5 @@
{
"usingComponents": {
"parser": "/components/parser/parser"
"mp-html": "/components/mp-html/index"
}
}

View File

@ -8,7 +8,7 @@
<span class="page__desc">{{ifrom}}</span>
</view>
<view class="weui-article__p">
<parser html="{{content}}" domain="{{domain}}" selectable/>
<mp-html content="{{content}}" domain="{{domain}}"/>
</view>
</view>
</view>

View File

@ -1,6 +1,6 @@
{
"usingComponents": {
"parser": "/components/parser/parser"
"mp-html": "/components/mp-html/index"
},
"navigationBarTitleText": "收藏集"
}

View File

@ -8,7 +8,7 @@
<span style="color:gray"> {{tm_current.questioncat_name}}</span>
</view>
<view class="weui-article__title">
<rich-text nodes="{{tm_current.name}}"></rich-text>
<mp-html content="{{tm_current.name}}" domain="{{domain}}"/>
</view>
<view wx:if="{{tm_current.img}}" style="text-align:center"><image src="{{tm_current.img}}" mode="aspectFit"></image></view>
</view>
@ -17,7 +17,9 @@
<label class="weui-cell weui-cell_active weui-check__label" wx:for="{{options}}" wx:key="key">
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%">
<mp-html content="{{item.value}}" domain="{{domain}}"/>
</view>
</view>
<view class="weui-cell__ft">
<radio class="weui-check" value="{{item.key}}" checked="{{item.checked}}" />
@ -32,7 +34,9 @@
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%">
<mp-html content="{{item.value}}" domain="{{domain}}"/>
</view>
</view>
<view class="weui-cell__hd">
<checkbox class="weui-check" value="{{item.key}}" checked="{{item.checked}}"/>

View File

@ -1,6 +1,6 @@
{
"usingComponents": {
"parser": "/components/parser/parser"
"mp-html": "/components/mp-html/index"
},
"navigationBarTitleText": "错题集"
}

View File

@ -14,7 +14,7 @@
<span style="color:gray"> {{currentTm.questioncat_name}}</span>
</view>
<view class="weui-article__title">
<rich-text nodes="{{currentTm.name}}"></rich-text>
<mp-html content="{{currentTm.name}}" domain="{{domain}}"/>
</view>
<view wx:if="{{currentTm.img}}" style="text-align:center"><image src="{{currentTm.img}}" mode="aspectFit"></image></view>
</view>
@ -23,7 +23,7 @@
<label class="weui-cell weui-cell_active weui-check__label" wx:for="{{options}}" wx:key="key">
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%"><mp-html content="{{item.value}}" domain="{{domain}}"/></view>
</view>
<view class="weui-cell__ft">
<radio class="weui-check" value="{{item.key}}" checked="{{item.checked}}" />
@ -38,7 +38,7 @@
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%"><mp-html content="{{item.value}}" domain="{{domain}}"/></view>
</view>
<view class="weui-cell__hd">
<checkbox class="weui-check" value="{{item.key}}" checked="{{item.checked}}"/>

View File

@ -0,0 +1,127 @@
// pages/qtest/form.js
const api = require("../../utils/request.js");
Page({
/**
* 页面的初始数据
*/
data: {
userInfo:{},
form:{
deptname:null,
code:null
}
},
deptInput: function(e){
this.data.form.deptname = e.detail.value
},
codeinput: function(e){
this.data.form.code = e.detail.value
},
submit: function(){
if(this.data.form.deptname&&this.data.form.code){
wx.showToast({
title: '加入考试成功',
icon:'none'
})
let data = this.data.form
api.request('/examtest/exam/attend/','POST', data).then(res=>{
wx.showLoading({
title: '正在组卷。。。',
mask:true
})
let exam = res.data.exam
api.request(`/examtest/exam/${exam}/init/`,'POST', data).then(res=>{
wx.hideLoading()
try {
wx.setStorageSync('examtest', res.data)
} catch (e) { }
wx.navigateTo({
url: '/pages/exam/note',
})
}).catch(e=>{
wx.hideLoading({
success: (res) => {},
})
})
})
}else{
wx.showToast({
title: '信息不完整',
icon:'none'
})
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
wx.showLoading({
title: '获取信息...',
})
api.request('/crm/consumer/info/','GET').then(res=>{
wx.hideLoading()
getApp().globalData.userinfo = res.data.userinfo
this.setData(
{
userinfo: res.data.userinfo,
form:{
deptname:res.data.userinfo.deptname
},
}
)
}).catch(e=>{wx.hideLoading()})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

View File

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@ -0,0 +1,60 @@
<view >
<view class="weui-form__text-area" style="margin-top:20px;padding: 0 18px;">
<h2 class="weui-form__title">正式考试</h2>
<view class="weui-form__desc">请确认和完善个人信息,并输入考试号加入考试,如有问题请及时联系老师</view>
</view>
<view>
<view class="weui-cells__title">个人信息</view>
<view class="weui-cells weui-cells_form">
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd"><label class="weui-label">姓名</label></view>
<view class="weui-cell__bd">
{{userinfo.name}}
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd"><label class="weui-label">身份证号</label></view>
<view class="weui-cell__bd">
{{userinfo.ID_number1}}
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">单位</label>
</view>
<view class="weui-cell__bd">
{{userinfo.company_name}}
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">部门</label>
</view>
<view class="weui-cell__bd">
<!-- <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 class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">考试号</label>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="填写要加入的考试号" placeholder-class="weui-input__placeholder" bindinput="codeinput" >
</input>
</view>
</view>
</view>
</view>
<view class="weui-form__tips-area">
<!-- <view class="weui-form__tips">
表单页提示,居中对齐
</view> -->
</view>
<view class="weui-form__opr-area">
<a class="weui-btn weui-btn_primary" bindtap="submit">确认</a>
</view>
</view>

View File

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

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

@ -1,6 +1,6 @@
{
"usingComponents": {
"parser": "/components/parser/parser"
"mp-html": "/components/mp-html/index"
},
"navigationBarTitleText": "练习"
}

View File

@ -13,8 +13,7 @@
<span class="txlabel">{{tm_current.type}}</span>
</view>
<view class="weui-article__title">
<!-- <parser html="{{tm_current.name}}" domain="{{domain}}" selectable/> -->
<rich-text nodes="{{tm_current.name}}"></rich-text>
<mp-html content="{{tm_current.name}}" />
</view>
<view wx:if="{{tm_current.img}}" style="text-align:center"><image src="{{tm_current.img}}" mode="aspectFit"></image></view>
</view>
@ -25,15 +24,13 @@
<view style="font-weight:bold;color:green" wx:if="{{ moShi == '看题模式' && item.right}}">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%">
<!-- <parser html="{{item.value}}" domain="{{domain}}" selectable /> -->
<rich-text nodes="{{item.value}}"></rich-text>
<mp-html content="{{item.value}}" />
</view>
</view>
<view wx:else>
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%">
<!-- <parser html="{{item.value}}" domain="{{domain}}" selectable /> -->
<rich-text nodes="{{item.value}}"></rich-text>
<mp-html content="{{item.value}}" />
</view>
</view>
</view>
@ -52,15 +49,13 @@
<view style="font-weight:bold;color:green" wx:if="{{ moShi == '看题模式' && item.right}}">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%">
<rich-text nodes="{{item.value}}"></rich-text>
<!-- <parser html="{{item.value}}" domain="{{domain}}" selectable /> -->
<mp-html content="{{item.value}}" domain="{{domain}}"/>
</view>
</view>
<view wx:else>
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%">
<rich-text nodes="{{item.value}}"></rich-text>
<!-- <parser html="{{item.value}}" domain="{{domain}}" selectable /> -->
<mp-html content="{{item.value}}" domain="{{domain}}"/>
</view>
</view>
</view>

View File

@ -4,21 +4,21 @@
<view class="weui-panel__bd">
<view class="weui-media-box weui-media-box_appmsg" hover-class="weui-cell_active" bindtap="register">
<view class="weui-media-box__hd weui-media-box__hd_in-appmsg" style="border-radius:6px;overflow:hidden">
<open-data type="userAvatarUrl" ></open-data>
<open-data type="userAvatarUrl"></open-data>
</view>
<view class="weui-media-box__bd weui-media-box__bd_in-appmsg">
<view class="weui-media-box__title" wx:if="{{userinfo && userinfo.username}}">
<span>账号:</span>
<span style="font-weight:bold;">{{userinfo.username}}</span>
</view>
<view class="weui-media-box__title" wx:else>账号:
<span style="color:red">未登陆</span>
<view class="weui-media-box__title" wx:else>账号:
<span style="color:red">未登陆</span>
</view>
<view class="weui-media-box__desc">昵称:
<open-data type="userNickName"></open-data>
</view>
<view class="weui-media-box__desc">单位:
<span wx:if="{{userinfo && userinfo.company_name}}">{{ userinfo.company_name }}</span>
<view class="weui-media-box__desc">单位:
<span wx:if="{{userinfo && userinfo.company_name}}">{{ userinfo.company_name }}</span>
</view>
<view class="weui-media-box__desc">如需解绑账号请联系课程顾问
</view>
@ -28,46 +28,55 @@
</view>
<view class="weui-cells__title">统计分析</view>
<view class="weui-cells weui-cells_after-title">
<view class="weui-cell" >
<view class="weui-cell">
<view class="weui-cell__hd">
<image src="/images/class.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
<image src="/images/class.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;">
</image>
</view>
<view class="weui-cell__bd">工作类别</view>
<view class="weui-cell__ft" style="font-weight:bold;color:darkblue"
wx:if="{{userinfo.workscope_name}}"
>{{userinfo.workscope_name}}</view>
<view class="weui-cell__ft" style="font-weight:bold;color:darkblue"
wx:else
>无</view>
<view class="weui-cell__ft" style="font-weight:bold;color:darkblue" wx:if="{{userinfo.workscope_name}}">
{{userinfo.workscope_name}}</view>
<view class="weui-cell__ft" style="font-weight:bold;color:darkblue" wx:else>无</view>
</view>
<!-- <l-list title="模考次数" tag-content="{{total}}" tag-position="right"/> -->
<view class="weui-cell">
<view class="weui-cell__hd">
<image src="/images/count.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
<image src="/images/count.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;">
</image>
</view>
<view class="weui-cell__bd">考次数</view>
<view class="weui-cell__bd">考次数</view>
<view class="weui-cell__ft" style="font-weight:bold;color:darkblue">{{total}}</view>
</view>
<view class="weui-cell">
<view class="weui-cell__hd">
<image src="/images/rate.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
<view class="weui-cell">
<view class="weui-cell__hd">
<image src="/images/rate.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;">
</image>
</view>
<view class="weui-cell__bd">通过率</view>
<view class="weui-cell__ft" style="font-weight:bold;color:darkblue">{{pass_rate}}%</view>
</view>
<view class="weui-cell">
<view class="weui-cell__hd">
<image src="/images/avg.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
<view class="weui-cell">
<view class="weui-cell__hd">
<image src="/images/avg.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;">
</image>
</view>
<view class="weui-cell__bd">平均分</view>
<view class="weui-cell__ft" style="font-weight:bold;color:darkblue" wx:if="{{avg_score}}">{{avg_score}}</view>
</view>
<navigator url="/pages/exam/index" class="weui-cell weui-cell_access" hover-class="weui-cell_active">
<view class="weui-cell__hd">
<image src="/images/exam.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;">
</image>
</view>
<view class="weui-cell__bd">正式考试入口</view>
<view class="weui-cell__ft weui-cell__ft_in-access" style="color:red"></view>
</navigator>
</view>
<view class="weui-footer weui-footer_fixed-bottom">
<!-- <view class="weui-footer__text" bindtap="intro">点击下载系统/小程序文档介绍</view> -->
<view class="weui-footer__text">更多服务请联系课程顾问</view>
</view>
<view class="weui-footer weui-footer_fixed-bottom">
<!-- <view class="weui-footer__text" bindtap="intro">点击下载系统/小程序文档介绍</view> -->
<view class="weui-footer__text">更多服务请联系课程顾问</view>
</view>
</view>
</view>

View File

@ -1,5 +1,5 @@
{
"usingComponents": {
"parser": "/components/parser/parser"
"mp-html": "/components/mp-html/index"
}
}

View File

@ -8,7 +8,7 @@
<span style="color:gray"> {{tm_current.questioncat_name}}</span>
</view>
<view class="weui-article__title">
<rich-text nodes="{{tm_current.name}}"></rich-text>
<mp-html content="{{tm_current.name}}" domain="{{domain}}"/>
</view>
<view wx:if="{{tm_current.img}}" style="text-align:center"><image src="{{tm_current.img}}" mode="aspectFit"></image></view>
</view>
@ -18,7 +18,7 @@
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%">
<rich-text nodes="{{item.value}}"></rich-text>
<mp-html content="{{item.value}}" domain="{{domain}}"/>
</view>
</view>
<view class="weui-cell__ft">
@ -34,7 +34,7 @@
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%"><mp-html content="{{item.value}}" domain="{{domain}}"/></view>
</view>
<view class="weui-cell__hd">
<checkbox class="weui-check" value="{{item.key}}" checked="{{item.checked}}"/>

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,5 @@
{
"usingComponents": {
"parser": "/components/parser/parser"
"mp-html": "/components/mp-html/index"
}
}

View File

@ -8,8 +8,7 @@
<span style="color:gray">({{tm_current.question.questioncat_name}})</span>
</view>
<view class="weui-article__title">
<!-- <parser html="{{tm_current.question.name}}" domain="{{domain}}" selectable/> -->
<rich-text nodes="{{tm_current.question.name}}"></rich-text>
<mp-html content="{{tm_current.question.name}}" domain="{{domain}}"/>
</view>
<view wx:if="{{tm_current.img}}" style="text-align:center"><image src="{{tm_current.img}}" mode="aspectFit"></image></view>
</view>
@ -18,7 +17,7 @@
<label class="weui-cell weui-cell_active weui-check__label" wx:for="{{options}}" wx:key="key">
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%"><mp-html content="{{item.value}}" domain="{{domain}}"/></view>
</view>
<view class="weui-cell__ft">
<radio class="weui-check" value="{{item.key}}" checked="{{item.checked}}" />
@ -33,7 +32,7 @@
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%"><mp-html content="{{item.value}}" domain="{{domain}}"/></view>
</view>
<view class="weui-cell__hd">
<checkbox class="weui-check" value="{{item.key}}" checked="{{item.checked}}"/>

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

@ -1,6 +1,6 @@
{
"usingComponents": {
"parser": "/components/parser/parser"
"mp-html": "/components/mp-html/index"
},
"navigationBarTitleText": "答题中"
}

View File

@ -17,7 +17,7 @@
<span style="color:gray">({{tm_current.questioncat_name}})</span>
</view>
<view class="weui-article__title">
<rich-text nodes="{{tm_current.name}}"></rich-text>
<mp-html content="{{tm_current.name}}" domain="{{domain}}"/>
</view>
<view wx:if="{{tm_current.img}}" style="text-align:center"><image src="{{tm_current.img}}" mode="aspectFit"></image></view>
</view>
@ -26,7 +26,9 @@
<label class="weui-cell weui-cell_active weui-check__label" wx:for="{{options}}" wx:key="key">
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%">
<mp-html content="{{item.value}}" domain="{{domain}}"/>
</view>
</view>
<view class="weui-cell__ft">
<radio class="weui-check" value="{{item.key}}" checked="{{item.checked}}" />
@ -41,7 +43,9 @@
<view class="weui-cell__bd">
<view style="float:left;width:10%">{{item.key}}:</view>
<view style="float:left;width:90%"><rich-text nodes="{{item.value}}"></rich-text></view>
<view style="float:left;width:90%">
<mp-html content="{{item.value}}" domain="{{domain}}"/>
</view>
</view>
<view class="weui-cell__hd">
<checkbox class="weui-check" value="{{item.key}}" checked="{{item.checked}}"/>

View File

@ -21,15 +21,13 @@
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"useMultiFrameRuntime": true,
"useMultiFrameRuntime": false,
"useApiHook": true,
"useApiHostProcess": false,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"bundle": false,
"useIsolateContext": true,
"useCompilerModule": true,

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2021-03-13 03:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cms', '0007_auto_20200811_0835'),
]
operations = [
migrations.AddField(
model_name='material',
name='sort',
field=models.IntegerField(default=1, verbose_name='排序码'),
),
]

View File

@ -37,4 +37,5 @@ class Material(CommonModel):
poster = models.CharField(max_length=10000, verbose_name='封面地址', null=True, blank=True)
type = models.CharField('格式', default='文档', max_length=50)
down_count = models.IntegerField('阅读量', default=0)
sort = models.IntegerField('排序码', default=1)

View File

@ -68,7 +68,7 @@ class MaterialViewSet(ModelViewSet):
filter_backends = [DjangoFilterBackend,SearchFilter, OrderingFilter]
search_fields = ['name','description']
ordering_fields = ['update_time', 'down_count']
ordering = ['-down_count']
ordering = ['sort', '-down_count']
filterset_fields = ['type', 'name']
@action(methods=['get'], detail=True, url_name='down_material', perms_map=[{'*':'down_material'}])

View File

@ -0,0 +1,23 @@
# Generated by Django 3.0.4 on 2021-03-13 03:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('crm', '0025_auto_20201013_1024'),
]
operations = [
migrations.AlterField(
model_name='consumer',
name='ID_number',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='查询身份证号'),
),
migrations.AlterField(
model_name='consumer',
name='realname',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='查询真实姓名'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2021-03-17 14:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('crm', '0026_auto_20210313_1109'),
]
operations = [
migrations.AddField(
model_name='consumer',
name='deptname',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='所在部门'),
),
]

View File

@ -74,6 +74,7 @@ class Consumer(CommonModel):
ID_number1 = models.CharField('身份证号', max_length=100, null=True, blank=True)
create_admin = models.ForeignKey(UserProfile, on_delete=models.SET_NULL, null=True, blank=True, related_name='consumer_create_admin')
deptname = models.CharField('所在部门', max_length=100, null=True, blank=True)
exceed_date = models.DateField('账号过期', null=True, blank=True)

View File

@ -9,7 +9,7 @@ from django.db.models import Q
from django_filters.rest_framework import DjangoFilterBackend
from openpyxl import Workbook, load_workbook
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.decorators import action, authentication_classes, permission_classes
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@ -365,17 +365,6 @@ class ConsumerViewSet(ModelViewSet):
request.user.save()
return Response(status=status.HTTP_200_OK)
# @action(methods=['put'], detail=False,
# url_path='process', url_name='exercise_processcat', perms_map=[{'*':'*'}])
# def processcat(self, request):
# '''
# 练习进度单类别修改
# '''
# if hasattr(request.user, 'process'):
# request.user.process = request.data['process']
# request.user.save()
# return Response(status=status.HTTP_200_OK)
@action(methods=['post'], detail=False,
url_path='realinfo', url_name='get_realinfo', perms_map=[{'*':'*'}])
def realinfo(self, request):
@ -547,6 +536,11 @@ class ConsumerViewSet(ModelViewSet):
except:
raise Http404
@action(methods=['get'], detail = False, perms_map=[{'*':'*'}])
def info(self, request, *args, **kwargs):
serializer = ConsumerDetailSerializer(instance=request.user)
return Response({"userinfo":serializer.data})
from .permission import get_consumerperm_list
class ConsumerMPLoginView(APIView):
"""

View File

@ -1,13 +0,0 @@
from sympy import *
a1 = Symbol('a1')
a2 = Symbol('a2')
a3 = Symbol('a3')
b1 = Symbol('b1')
b2 = Symbol('b2')
r = solve([
a1+a2-40,
b1+b2-10,
2*a1+4*b1-60,
2*a2+4*b2-40
], [a1,a2,b1,b2])
print(r)

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,53 @@
# Generated by Django 3.0.4 on 2021-03-14 09:16
from django.conf import settings
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),
('examtest', '0024_auto_20200402_2353'),
]
operations = [
migrations.AddField(
model_name='workscope',
name='can_exam',
field=models.BooleanField(default=False, verbose_name='是否可组织考试'),
),
migrations.AlterField(
model_name='examtest',
name='type',
field=models.CharField(choices=[('自助模考', '自助模考'), ('押卷模考', '押卷模考'), ('正式考试', '正式考试')], default='自助模考', max_length=50, verbose_name='考试类型'),
),
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_delete', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('code', models.CharField(blank=True, max_length=100, null=True, verbose_name='考试编号')),
('name', models.CharField(max_length=100, verbose_name='名称')),
('place', models.CharField(blank=True, max_length=100, null=True, verbose_name='考试地点')),
('opentime', models.DateTimeField(blank=True, null=True, verbose_name='开启时间')),
('closetime', models.DateTimeField(blank=True, null=True, verbose_name='关闭时间')),
('proctor_name', models.CharField(max_length=100, verbose_name='监考人姓名')),
('proctor_phone', models.CharField(max_length=100, verbose_name='监考人联系方式')),
('create_admin', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='exam_create_admin', to=settings.AUTH_USER_MODEL)),
('workscope', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='examtest.WorkScope', verbose_name='工作类别')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='examtest',
name='exam',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='examtest_exam', to='examtest.Exam', verbose_name='关联的正式考试'),
),
]

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

@ -1,11 +1,29 @@
from django.db import models
from rbac.models import SoftCommonModel, CommonModel
from rbac.models import SoftCommonModel, CommonModel, UserProfile
from django.contrib.postgres.fields import JSONField, ArrayField
from question.models import Questioncat, Question
from crm.models import Consumer
from .models_paper import WorkScope, Paper
class Exam(CommonModel):
"""
组织的正式考试
"""
code = models.CharField('考试编号', max_length=100, null=True, blank=True)
name = models.CharField('名称', max_length=100)
place = models.CharField('考试地点', max_length=100, null=True, blank=True)
workscope = models.ForeignKey(WorkScope, verbose_name='工作类别', on_delete= models.DO_NOTHING)
opentime = models.DateTimeField('开启时间', null=True, blank=True)
closetime = models.DateTimeField('关闭时间', null=True, blank=True)
proctor_name = models.CharField('监考人姓名', max_length=100)
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):
'''
考试记录表
@ -13,6 +31,7 @@ class ExamTest(CommonModel):
type_choices = (
('自助模考', '自助模考'),
('押卷模考', '押卷模考'),
('正式考试', '正式考试')
)
name = models.CharField(max_length=200, verbose_name='名称')
type = models.CharField(max_length=50, default='自助模考',choices = type_choices, verbose_name='考试类型')
@ -25,15 +44,17 @@ 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)
class Meta:
verbose_name = '模拟考试'
verbose_name_plural = verbose_name
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='本题得分')
@ -47,4 +68,4 @@ class Banner(CommonModel):
name = models.CharField(max_length=200, verbose_name='名称')
path = models.CharField(max_length=1000, null=True, blank=True, verbose_name='图片地址')
url = models.CharField(max_length=1000, null=True, blank=True, verbose_name='链接地址')
sort = models.IntegerField(default=1, verbose_name='排序数字')
sort = models.IntegerField(default=1, verbose_name='排序数字')

View File

@ -1,4 +1,5 @@
from django.db import models
from django.template.defaultfilters import default
from rbac.models import SoftCommonModel, CommonModel
from django.contrib.postgres.fields import JSONField, ArrayField
from question.models import Questioncat, Question
@ -31,6 +32,7 @@ class WorkScope(CommonModel):
subject = models.ForeignKey(Questioncat, verbose_name='所属学科', on_delete=models.CASCADE , related_name='workscope_subject')
questioncat = models.ManyToManyField(Questioncat, verbose_name='所选科目')
rule = models.ForeignKey(TestRule, on_delete=models.CASCADE, verbose_name='试卷结构')
can_exam = models.BooleanField('是否可组织考试', default=False)
class Meta:
verbose_name = '工作类别'

View File

@ -1,7 +1,7 @@
from rest_framework import serializers
from question.models import Questioncat
from crm.models import Consumer
from .models import ExamTest, AnswerDetail, Banner
from .models import Exam, ExamTest, AnswerDetail, Banner
from .models_paper import TestRule, WorkScope, Paper, PaperQuestions
from question.serializers import QuestionSerializer
@ -41,7 +41,7 @@ class WorkScopeSerializer(serializers.ModelSerializer):
class MoniTestSerializer(serializers.ModelSerializer):
"""
考序列化
序列化
"""
start_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
end_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
@ -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):
@ -163,3 +164,55 @@ class PaperQuestionsCreateSerializer(serializers.ModelSerializer):
model = PaperQuestions
fields = '__all__'
class ExamCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Exam
fields = ['name', 'place', 'opentime', 'closetime', 'proctor_name', 'proctor_phone', 'workscope']
class ExamListSerializer(serializers.ModelSerializer):
create_admin_username = serializers.StringRelatedField(source='create_admin')
workscope_name = serializers.StringRelatedField(source='workscope')
class Meta:
model = Exam
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

@ -1,5 +1,5 @@
from django.urls import path,include
from .views import TestRuleViewSet, AnswerDetailView, WorkScopeViewSet, BannerViewSet, ExamTestViewSet, PaperViewSet
from .views import TestRuleViewSet, AnswerDetailView, WorkScopeViewSet, BannerViewSet, ExamTestViewSet, PaperViewSet, ExamViewSet
from rest_framework import routers
@ -9,6 +9,7 @@ router.register('workscope', WorkScopeViewSet, basename="workscope")
router.register('banner', BannerViewSet, basename='banner')
router.register('examtest', ExamTestViewSet, basename='examtest')
router.register('paper', PaperViewSet, basename='paper')
router.register('exam', ExamViewSet, basename='exam')
urlpatterns = [
path('answerdetail/', AnswerDetailView.as_view()),

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
@ -18,18 +20,123 @@ from question.models import Question
from question.serializers import QuestionSerializer
from server import settings
from utils.custom import CommonPagination
from utils.mixins import OptimizationMixin
from .exports import export_test
from .models import AnswerDetail, Banner, ExamTest
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)
WorkScopeSerializer, ExamCreateUpdateSerializer, ExamListSerializer, ExamTestDetailSerializer)
from django.utils import timezone
# Create your views here.
class ExamViewSet(ModelViewSet):
"""
正式考试增删改查
"""
perms_map = [
{'get': 'exam_view'}, {'post': 'exam_create'},
{'put': 'exam_update'}, {'delete': 'exam_delete'}]
pagination_class = CommonPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
ordering = ['-id']
search_fields = ['name']
def get_queryset(self):
queryset = Exam.objects.all()
if not self.request.user.is_superuser:
queryset = queryset.filter(create_admin = self.request.user)
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化
return queryset
def get_serializer_class(self):
if self.action in ['create', 'update']:
return ExamCreateUpdateSerializer
return ExamListSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = serializer.save(create_admin=request.user)
instance.code = instance.pk + 10000
instance.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if ExamTest.objects.filter(exam=instance).exists():
return Response({'error':'存在考试记录,禁止删除'})
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@action(methods=['post'], detail = False,perms_map=[{'post':'exam_attend'}])
def attend(self, request, *args, **kwargs):
"""
参加考试
"""
if request.data.get('deptname', None):
request.user.deptname = request.data['deptname']
request.user.save()
if request.data.get('code', None):
code = request.data.get('code')
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':'考试编号不存在'})
return Response({'error':'操作失败'})
@action(methods=['post'], detail = True,perms_map=[{'post':'exam_attend'}])
def init(self, request, *args, **kwargs):
"""
生成考试卷
"""
obj = Exam.objects.get(pk=kwargs['pk'])
workscope = obj.workscope
ret = {}
if workscope.name in ['医学Ⅲ类', '非医学Ⅲ类']:
ret['name'] = obj.name
ret['type'] = '正式考试' # 正式考试
ret['exam'] = kwargs['pk']
ret['exam_'] = ExamListSerializer(instance=obj).data
ret['workscope'] = workscope.id
ret['limit'] = 60
ret['total_score'] = 120
ret['pass_score'] = 90
ret['danxuan_count'] = 40
ret['danxuan_score'] = 2
ret['duoxuan_count'] = 10
ret['duoxuan_score'] = 4
question_queryset = Question.objects.none()
questioncats = workscope.questioncat.order_by('type', 'create_time')
if questioncats.count() == 3:
queryset = Question.objects.filter(is_delete=0)
a1_set = queryset.filter(questioncat=questioncats[0], type='单选').order_by('?')[:12]
a2_set = queryset.filter(questioncat=questioncats[1], type='单选').order_by('?')[:12]
a3_set = queryset.filter(questioncat=questioncats[2], type='单选').order_by('?')[:16]
b1_set = queryset.filter(questioncat=questioncats[0], type='多选').order_by('?')[:3]
b2_set = queryset.filter(questioncat=questioncats[1], type='多选').order_by('?')[:3]
b3_set = queryset.filter(questioncat=questioncats[2], type='多选').order_by('?')[:4]
question_queryset = question_queryset|a1_set|a2_set|a3_set|b1_set|b2_set|b3_set
questions = QuestionSerializer(instance=question_queryset.order_by('type'),many=True).data
for i in questions:
if i['type'] == '单选':
i['total_score'] = 2
elif i['type'] == '多选':
i['total_score'] = 4
ret['questions'] = questions
return Response(ret)
return Response({'error':'生成试卷失败'})
class AnswerDetailView(APIView):
authentication_classes = []
@ -56,8 +163,8 @@ class WorkScopeViewSet(ModelViewSet):
ordering_fields = ('id',)
ordering = ['id']
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['subject']
search_fields = ('^name',)
filterset_fields = ['subject', 'can_exam']
search_fields = ('name',)
def get_queryset(self):
queryset = self.queryset
@ -102,6 +209,7 @@ class WorkScopeViewSet(ModelViewSet):
b3_set = queryset.filter(questioncat=questioncats[2], type='多选').order_by('?')[:b3]
question_queryset = question_queryset|a1_set|a2_set|a3_set|b1_set|b2_set|b3_set
elif workscope.name == '辐射安全管理':
# 辐射安全管理出卷规则
queryset = Question.objects.filter(is_delete=0)
a1_set = queryset.filter(questioncat=questioncats[0], type='单选').order_by('?')[:16]
a2_set = queryset.filter(questioncat=questioncats[1], type='单选').order_by('?')[:24]
@ -109,6 +217,7 @@ class WorkScopeViewSet(ModelViewSet):
b2_set = queryset.filter(questioncat=questioncats[1], type='多选').order_by('?')[:6]
question_queryset = question_queryset|a1_set|a2_set|b1_set|b2_set
elif workscope.name == '科研、生产及其他':
# 科研、生产及其他出卷规则
queryset = Question.objects.filter(is_delete=0)
a1_set = queryset.filter(questioncat=questioncats[0], type='单选').order_by('?')[:24]
a2_set = queryset.filter(questioncat=questioncats[1], type='单选').order_by('?')[:16]
@ -233,7 +342,7 @@ class ExamTestViewSet(ModelViewSet):
"""
考试记录列表和详情
"""
perms_map = [{'get': 'examtest_view'},{'post': '*'}]
perms_map = [{'get': 'examtest_view'},{'post': '*'}, {'delete':'examtest_delete'}]
pagination_class = CommonPagination
queryset = ExamTest.objects.filter(is_delete=0).all()
serializer_class = ExamTestListSerializer
@ -241,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, (
@ -302,6 +411,10 @@ class ExamTestViewSet(ModelViewSet):
serializer_detail = AnswerDetailCreateSerializer(data=questions, many=True)
if serializer_detail.is_valid():
serializer_detail.save()
# 关联正式考试如有
if request.data.get('exam', None):
instance.exam = Exam.objects.get(pk=request.data['exam'])
instance.save()
return Response(MoniTestSerializer(instance).data,status=status.HTTP_200_OK)
else:
return Response(serializer_detail.errors)
@ -321,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 Response({'path': path})
class PaperViewSet(ModelViewSet):
"""

View File

@ -87,7 +87,7 @@ class QuestioncatViewSet(ModelViewSet):
serializer = QuestioncatSerializer(instance=queryset,many=True)
return Response(serializer.data)
from django.db.models import Q
class QuestionViewSet(ModelViewSet):
"""
题目增删改查
@ -126,12 +126,19 @@ class QuestionViewSet(ModelViewSet):
@action(methods=['get'], detail=False,
url_path='correct', url_name='correct_question', permission_classes=[IsAuthenticated])
def correct(self, request):
for i in Question.objects.all():
# for i in Question.objects.all():
# options = i.options
# for k in options:
# options[k] = str(options[k]).replace('<p>','').replace('</p>','')\
# .replace('&times;','×').replace('&beta;','β').replace('&alpha;','α')\
# .replace('&gamma;','γ').replace('&nbsp;',' ')
# i.options = options
# i.save()
for i in Question.objects.filter(Q(options__A__contains='&mu;')|Q(options__B__contains='&mu;')|Q(options__C__contains='&mu;')|Q(options__D__contains='&mu;')|Q(options__E__contains='&mu;')|Q(options__F__contains='&mu;')):
print(i)
options = i.options
for k in options:
options[k] = str(options[k]).replace('<p>','').replace('</p>','')\
.replace('&times;','×').replace('&beta;','β').replace('&alpha;','α')\
.replace('&gamma;','γ').replace('&nbsp;',' ')
options[k] = str(options[k]).replace('&mu;','μ')
i.options = options
i.save()
return Response()

Binary file not shown.

View File

@ -0,0 +1,48 @@
from django.db.models.query import QuerySet
class CreateUpdateModelAMixin:
"""
业务用基本表A用
"""
def perform_create(self, serializer):
serializer.save(create_by = self.request.user)
def perform_update(self, serializer):
serializer.save(update_by = self.request.user)
class CreateUpdateModelBMixin:
"""
业务用基本表B用
"""
def perform_create(self, serializer):
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept)
def perform_update(self, serializer):
serializer.save(update_by = self.request.user)
class CreateUpdateCustomMixin:
"""
整合
"""
def perform_create(self, serializer):
if hasattr(self.queryset.model, 'belong_dept'):
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept)
else:
serializer.save(create_by = self.request.user)
def perform_update(self, serializer):
serializer.save(update_by = self.request.user)
class OptimizationMixin:
"""
性能优化,需要在序列化器里定义setup_eager_loading,可在必要的View下继承
"""
def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, QuerySet):
# Ensure queryset is re-evaluated on each request.
queryset = queryset.all()
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化
return queryset

15
test_server/x.py Normal file
View File

@ -0,0 +1,15 @@
from sympy import *
x1 = Symbol('x1')
x2 = Symbol('x2')
x3 = Symbol('x3')
y1 = Symbol('y1')
y2 = Symbol('y2')
y3 = Symbol('y3')
f = [
y1+y2+y3-10,
x1+x2+x3-40,
2*x1+4*y1-36,
2*x2+4*y2-36,
]
print(solve(f))