This commit is contained in:
caoqianming 2021-06-18 13:29:44 +08:00
commit 2d6a5459d6
19 changed files with 627 additions and 58 deletions

View File

@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getCandidateList(query) {
return request({
url: '/crm/candidate/',
method: 'get',
params:query
})
}

View File

@ -252,13 +252,19 @@ export const asyncRoutes = [
component: Layout,
redirect: '/exammanage/index',
name: 'Exammanage',
meta: { title: '考管理', icon: ''},
meta: { title: '考管理', icon: 'component'},
children: [
{
path: 'index',
name: 'exam',
component: () => import('@/views/exam/index.vue'),
meta: { title: '考试管理', icon: 'component', perms: ['exam_view'] }
meta: { title: '正式考试', perms: ['exam_view'] }
},
{
path: 'issue',
name: 'issue',
component: () => import('@/views/exam/issue.vue'),
meta: { title: '出证记录', perms: ['candidate_view'] }
},
]
},

View File

@ -0,0 +1,126 @@
<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
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.number }}</template>
</el-table-column>
<el-table-column label="姓名">
<template slot-scope="scope">{{ scope.row.consumer_name }}</template>
</el-table-column>
<el-table-column label="身份证号">
<template slot-scope="scope">{{ scope.row.ID_number }}</template>
</el-table-column>
<el-table-column label="工作类别">
<template slot-scope="scope">{{ scope.row.workscope_name }}</template>
</el-table-column>
<el-table-column label="单位">
<template slot-scope="scope">{{ scope.row.company_name }}</template>
</el-table-column>
<el-table-column label="有效期始">
<template slot-scope="scope">{{ scope.row.start_date }}</template>
</el-table-column>
<el-table-column label="有效期止">
<template slot-scope="scope">{{ scope.row.end_date }}</template>
</el-table-column>
<el-table-column label="创建人">
<template slot-scope="scope">
<span>{{ scope.row.create_admin_username }}</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)"
>查看证书</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"
/>
</div>
</template>
<script>
import { getCandidateList } from "@/api/candidate";
import { deepClone } from "@/utils";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination"
const listQuery = {
page: 1,
limit: 20,
search: ""
}
export default {
components: { Pagination },
data() {
return {
listQuery:listQuery,
tableData: {count:0},
listLoading: true,
};
},
computed: {},
created() {
this.getList();
},
methods: {
checkPermission,
getList() {
this.listLoading = true
getCandidateList(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();
},
handleView(scope){
window.open('https://apitest.ahctc.cn/crm/candidate/img?id='+scope.row.id)
}
}
};
</script>

View File

@ -39,7 +39,8 @@
"pages/admin/exam/index",
"pages/candidate/show",
"pages/admin/candidate/index",
"pages/candidate/my"
"pages/candidate/my",
"pages/admin/exam/upimg"
],
"window": {
"backgroundTextStyle": "light",

View File

@ -6,7 +6,10 @@ Page({
* 页面的初始数据
*/
data: {
exam:{},
exam:{
qdimgs:[],
xcimgs:[]
},
tests:[]
},
@ -16,11 +19,7 @@ Page({
onLoad: function (options) {
if(options.id){
let id = options.id
api.requesta(`/examtest/exam/${id}/`, 'GET').then(res=>{
this.setData({
exam:res.data
})
})
this.data.exam.id = id
api.requesta('/examtest/examtest/', 'GET', {exam:id, pageoff:true}).then(res=>{
this.setData({
tests:res.data
@ -28,6 +27,34 @@ Page({
})
}
},
getExam: function(){
let id = this.data.exam.id
api.requesta(`/examtest/exam/${id}/`, 'GET').then(res=>{
let exam = res.data
for(var i=0;i<exam.qdimgs.length;i++){
exam.qdimgs[i] = getApp().globalData.host+exam.qdimgs[i]
}
for(var i=0;i<exam.xcimgs.length;i++){
exam.xcimgs[i] = getApp().globalData.host+exam.xcimgs[i]
}
this.setData({
exam:exam
})
})
},
previewImage: function (e) {
if(e.currentTarget.dataset.type=='qdimgs'){
wx.previewImage({
current: e.currentTarget.id, // 当前显示图片的http链接
urls: this.data.exam.qdimgs // 需要预览的图片http链接列表
})
}else{
wx.previewImage({
current: e.currentTarget.id, // 当前显示图片的http链接
urls: this.data.exam.xcimgs // 需要预览的图片http链接列表
})
}
},
issue: function(){
wx.showLoading({
title: '正在生成...',
@ -45,7 +72,7 @@ Page({
* 生命周期函数--监听页面显示
*/
onShow: function () {
this.getExam()
},
/**
@ -108,5 +135,10 @@ Page({
wx.navigateTo({
url: '/pages/candidate/show?id='+candidate_.id,
})
},
upImg: function(){
wx.navigateTo({
url: '/pages/admin/exam/upimg?id='+this.data.exam.id,
})
}
})

View File

@ -1,5 +1,5 @@
<view class="page">
<view class="page__bd">
<view class="page__bd">
<view style="height:4px"></view>
<view class="weui-panel weui-panel_access" style="margin:8px">
<view class="weui-panel__hd">{{exam.name}}</view>
@ -19,6 +19,52 @@
监考人:{{exam.proctor_name}}-{{exam.proctor_phone}}
</view>
</view>
<view style="text-align:right;padding-right:8rpx" wx:if="{{exam.qdimgs.length==0}}">
<button type="primary" size="mini" bindtap="upImg" data-id="{{exam.id}}">上传图片</button>
</view>
<view class="weui-cell" style="padding:8px;font-size:14px;" wx:if="{{exam.qdimgs.length>0}}">
<view class="weui-cell__bd">
<view class="weui-uploader">
<view class="weui-uploader__hd">
<view class="weui-uploader__overview">
<view class="weui-uploader__title">签到表图片</view>
</view>
</view>
<view class="weui-uploader__bd">
<view class="weui-uploader__files" id="uploaderFiles">
<block wx:for="{{exam.qdimgs}}" wx:key="*this">
<view class="weui-uploader__file" bindtap="previewImage" id="{{item}}" data-type="qdimgs"
data-index="{{index}}">
<image class="weui-uploader__img" src="{{item}}" mode="aspectFill" />
</view>
</block>
</view>
</view>
</view>
</view>
</view>
<view class="weui-cell" style="padding:8px;font-size:14px;" wx:if="{{exam.xcimgs.length>0}}">
<view class="weui-cell__bd">
<view class="weui-uploader">
<view class="weui-uploader__hd">
<view class="weui-uploader__overview">
<view class="weui-uploader__title">现场照片</view>
</view>
</view>
<view class="weui-uploader__bd">
<view class="weui-uploader__files" id="uploaderFiles">
<block wx:for="{{exam.xcimgs}}" wx:key="*this">
<view class="weui-uploader__file" bindtap="previewImage" id="{{item}}" data-type="xcimgs"
data-index="{{index}}">
<image class="weui-uploader__img" src="{{item}}" mode="aspectFill" />
</view>
</block>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="weui-panel weui-panel_access">
@ -47,9 +93,12 @@
<text style="color:darkblue">{{item.candidate_.number}}</text>
</view>
<view style="text-align:right;padding-right:8rpx">
<button type="default" size="mini" bindtap="goCandidate" style="margin-left:4px" data-id="{{item.id}}" data-candidate_="{{item.candidate_}}" wx:if="{{item.candidate_}}">查看证书</button>
<button type="primary" size="mini" bindtap="issue" data-id="{{item.id}}" data-index="{{index}}" wx:else>颁发证书</button>
<button type="default" size="mini" bindtap="godetail" style="margin-left:4px" data-id="{{item.id}}">答卷详情</button>
<button type="default" size="mini" bindtap="goCandidate" style="margin-left:4px" data-id="{{item.id}}"
data-candidate_="{{item.candidate_}}" wx:if="{{item.candidate_}}">查看证书</button>
<button type="primary" size="mini" bindtap="issue" data-id="{{item.id}}" data-index="{{index}}"
wx:else>颁发证书</button>
<button type="default" size="mini" bindtap="godetail" style="margin-left:4px"
data-id="{{item.id}}">答卷详情</button>
</view>
</view>
@ -64,5 +113,5 @@
<view class="weui-loadmore weui-loadmore_line" wx:if="{{isLoadedAll}}">
<text class="weui-loadmore__tips">到底了</text>
</view>
</view>
</view>
</view>

View File

@ -0,0 +1,207 @@
// pages/admin/exam/u'p.js
const api = require("../../../utils/request.js");
Page({
/**
* 页面的初始数据
*/
data: {
exam:0,
qdimgs: [],
xcimgs: [],
upqdimgs:[],
upxcimgs:[]
},
chooseImage: function (e) {
var that = this;
wx.chooseImage({
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
// 返回选定照片的本地文件路径列表tempFilePath可以作为img标签的src属性显示图片
console.log(e.currentTarget.dataset.type)
if(e.currentTarget.dataset.type == 'qdimgs'){
that.setData({
qdimgs: that.data.qdimgs.concat(res.tempFilePaths)
});
}else{
that.setData({
xcimgs: that.data.xcimgs.concat(res.tempFilePaths)
});
}
}
})
},
previewImage: function (e) {
if(e.currentTarget.dataset.type=='qdimgs'){
wx.previewImage({
current: e.currentTarget.id, // 当前显示图片的http链接
urls: this.data.qdimgs // 需要预览的图片http链接列表
})
}else{
wx.previewImage({
current: e.currentTarget.id, // 当前显示图片的http链接
urls: this.data.xcimgs // 需要预览的图片http链接列表
})
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.data.exam = options.id
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
},
deleteImg: function(e){
var that = this
wx.showModal({
content: '确认删除该图片?',
success (res) {
if (res.confirm) {
if(e.currentTarget.dataset.type=='qdimgs'){
var qdimgs = that.data.qdimgs
qdimgs.splice(e.currentTarget.dataset.index, 1);
that.setData({
qdimgs:qdimgs
})
}else{
var xcimgs = that.data.xcimgs
xcimgs.splice(e.currentTarget.dataset.index, 1);
that.setData({
xcimgs:xcimgs
})
}
}
}
})
},
confirm: function(){
if(this.data.upqdimgs.length>0 && this.data.upxcimgs.length>0){
let formdata={
qdimgs:this.data.upqdimgs,
xcimgs:this.data.upxcimgs
}
let id = this.data.exam
wx.showLoading({
title: '正在提交...',
})
api.requesta(`/examtest/exam/${id}/upimgs/`,'POST', formdata).then(res=>{
wx.hideLoading({
success: (res) => {},
})
wx.navigateBack({
delta: 0,
})
})
}
else if(this.data.qdimgs.length>0 && this.data.xcimgs.length>0){
this.upImgx(0, 'qdimgs')
}
else{
wx.showToast({
title: '图片信息不全',
})
}
},
upImgx: function(index, type){
var that = this
if(type=='qdimgs'){
let x = index+1
wx.showLoading({
title: '正在上传签到图片',
})
wx.uploadFile({
filePath: that.data.qdimgs[index],
name: 'file',
url: getApp().globalData.host+'/uploadfile/',
header:{
'Authorization': 'JWT ' + getApp().globalData.admintoken
},
success (res){
wx.hideLoading()
let data = JSON.parse(res.data);
that.data.upqdimgs.push(data.data.path)
if(x == that.data.qdimgs.length){
that.upImgx(0, 'xcimgs')
}else{
that.upImgx(index+1, 'qdimgs')
}
}
})
}else{
let x = index+1
wx.showLoading({
title: '正在上传现场图片',
})
wx.uploadFile({
filePath: that.data.xcimgs[index],
name: 'file',
url: getApp().globalData.host+'/uploadfile/',
header:{
'Authorization': 'JWT ' + getApp().globalData.admintoken
},
success (res){
wx.hideLoading()
let data = JSON.parse(res.data);
that.data.upxcimgs.push(data.data.path)
if(x == that.data.xcimgs.length){
that.confirm()
}else{
that.upImgx(index+1, 'xcimgs')
}
}
})
}
}
})

View File

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

View File

@ -0,0 +1,65 @@
<view class="page">
<view class="page__bd">
<view class="weui-cells">
<view class="weui-cell">
<view class="weui-cell__bd">
<view class="weui-uploader">
<view class="weui-uploader__hd">
<view class="weui-uploader__overview">
<view class="weui-uploader__title">签到表图片</view>
<!-- <view class="weui-uploader__info">{{files.length}}/2</view> -->
</view>
<view class="weui-uploader__tips">
长按删除
</view>
</view>
<view class="weui-uploader__bd">
<view class="weui-uploader__files" id="uploaderFiles">
<block wx:for="{{qdimgs}}" wx:key="*this">
<view class="weui-uploader__file" bindtap="previewImage" id="{{item}}" data-type="qdimgs" bindlongpress="deleteImg"
data-index="{{index}}">
<image class="weui-uploader__img" src="{{item}}" mode="aspectFill" />
</view>
</block>
</view>
<view class="weui-uploader__input-box">
<view class="weui-uploader__input" bindtap="chooseImage" data-type="qdimgs"></view>
</view>
</view>
</view>
</view>
</view>
<view class="weui-cell">
<view class="weui-cell__bd">
<view class="weui-uploader">
<view class="weui-uploader__hd">
<view class="weui-uploader__overview">
<view class="weui-uploader__title">现场照片</view>
<!-- <view class="weui-uploader__info">{{files.length}}/2</view> -->
</view>
<view class="weui-uploader__tips">
长按删除
</view>
</view>
<view class="weui-uploader__bd">
<view class="weui-uploader__files" id="uploaderFiles">
<block wx:for="{{xcimgs}}" wx:key="*this">
<view class="weui-uploader__file" bindtap="previewImage" id="{{item}}" data-type="xcimgs" bindlongpress="deleteImg"
data-index="{{index}}">
<image class="weui-uploader__img" src="{{item}}" mode="aspectFill" />
</view>
</block>
</view>
<view class="weui-uploader__input-box">
<view class="weui-uploader__input" bindtap="chooseImage" data-type="xcimgs"></view>
</view>
</view>
</view>
</view>
</view>
</view>
<view style="margin-top:4px">
<button type="primary" bindtap="confirm" >确定上传</button>
</view>
</view>
</view>

View File

@ -0,0 +1 @@
/* pages/admin/exam/u'p.wxss */

View File

@ -63,15 +63,6 @@
</view>
</view>
<view class="weui-cells weui-cells_after-title" style="margin-top:6px">
<navigator url="/pages/quota/quota" class="weui-cell weui-cell_access" hover-class="weui-cell_active">
<view class="weui-cell__hd">
<image src="/images/quota.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">
<!-- <span wx:if="{{to_read}}">{{to_read}}条未读</span> -->
</view>
</navigator>
<navigator url="/pages/candidate/index" class="weui-cell weui-cell_access" hover-class="weui-cell_active">
<view class="weui-cell__hd">
<image src="/images/candidate.svg" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>

View File

@ -71,6 +71,15 @@
<view class="weui-cell__bd">个人防护用品检测(铅当量)</view>
<view class="weui-cell__ft weui-cell__ft_in-access" style="color:red"></view>
</navigator>
<navigator url="/pages/quota/quota" class="weui-cell weui-cell_access" hover-class="weui-cell_active">
<view class="weui-cell__hd">
<image src="/images/quota.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">
<!-- <span wx:if="{{to_read}}">{{to_read}}条未读</span> -->
</view>
</navigator>
</view>
<view class="weui-cells__title">管理员操作台</view>
<view class="weui-cells weui-cells_after-title">
@ -79,10 +88,10 @@
<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 weui-footer_fixed-bottom">
<view class="weui-footer__text">更多服务请联系课程顾问</view>
</view>
</view> -->
</view>

View File

@ -23,20 +23,20 @@
"compileHotReLoad": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": false,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"bundle": false,
"useIsolateContext": true,
"useCompilerModule": true,
"userConfirmedUseCompilerModuleSwitch": false,
"userConfirmedBundleSwitch": false,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true
"minifyWXSS": true,
"useApiHostProcess": false
},
"compileType": "miniprogram",
"libVersion": "2.15.0",

View File

@ -105,7 +105,7 @@ class SendCode(CommonModel):
code = models.CharField(max_length=4, verbose_name= '验证码')
class Candidate(CommonModel):
consumer = models.ForeignKey(Consumer, on_delete=models.CASCADE, related_name='candidate_consumer')
consumer = models.ForeignKey(Consumer, on_delete=models.DO_NOTHING, related_name='candidate_consumer')
workscope = models.ForeignKey(WorkScope, on_delete=models.DO_NOTHING, related_name='candidate_workscope')

View File

@ -76,6 +76,7 @@ class ConsumerDetailSerializer(serializers.ModelSerializer):
class CandidateSerializer(serializers.ModelSerializer):
create_admin_username = serializers.StringRelatedField(source='create_admin', read_only=True)
class Meta:
model = Candidate
fields = '__all__'

View File

@ -0,0 +1,24 @@
# Generated by Django 3.0.4 on 2021-06-13 14:29
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('examtest', '0030_examtest_consumer_detail'),
]
operations = [
migrations.AddField(
model_name='examtest',
name='qdimgs',
field=django.contrib.postgres.fields.jsonb.JSONField(default=list, verbose_name='签到图片'),
),
migrations.AddField(
model_name='examtest',
name='xcimgs',
field=django.contrib.postgres.fields.jsonb.JSONField(default=list, verbose_name='现场图片'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 3.0.4 on 2021-06-13 14:34
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('examtest', '0031_auto_20210613_2229'),
]
operations = [
migrations.RemoveField(
model_name='examtest',
name='qdimgs',
),
migrations.RemoveField(
model_name='examtest',
name='xcimgs',
),
migrations.AddField(
model_name='exam',
name='qdimgs',
field=django.contrib.postgres.fields.jsonb.JSONField(default=list, verbose_name='签到图片'),
),
migrations.AddField(
model_name='exam',
name='xcimgs',
field=django.contrib.postgres.fields.jsonb.JSONField(default=list, verbose_name='现场图片'),
),
]

View File

@ -21,6 +21,8 @@ class Exam(CommonModel):
create_admin = models.ForeignKey(UserProfile, on_delete=models.SET_NULL, null=True, blank=True, related_name='exam_create_admin')
chance = models.IntegerField('考试机会', default=3)
qdimgs = JSONField('签到图片', default=list)
xcimgs = JSONField('现场图片', default=list)
def __str__(self):
return self.name
@ -49,6 +51,7 @@ class ExamTest(CommonModel):
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

View File

@ -157,6 +157,13 @@ class ExamViewSet(ModelViewSet):
return Response(ret)
return Response({'error':'生成试卷失败'})
@action(methods=['post'], detail = True ,perms_map=[{'post':'exam_update'}])
def upimgs(self, request, *args, **kwargs):
obj = self.get_object()
obj.qdimgs = request.data.get('qdimgs', [])
obj.xcimgs = request.data.get('xcimgs', [])
obj.save()
return Response()
class AnswerDetailView(APIView):
@ -392,6 +399,7 @@ class ExamTestViewSet(PageOrNot, ModelViewSet):
filterset_fields = ['type','is_pass', 'exam']
def get_serializer_class(self):
if self.request:
if self.request.query_params.get('exam', None) or self.request.query_params.get('type') == '正式考试':
return ExamTestExamListSerializer
return ExamTestListSerializer
@ -536,6 +544,7 @@ class ExamTestViewSet(PageOrNot, ModelViewSet):
candidate.save()
return Response({"id":candidate.pk, "number":candidate.number})
class PaperViewSet(ModelViewSet):
"""
押题卷增删改查