小程序端

This commit is contained in:
caoqianming 2021-06-07 17:45:01 +08:00
parent 806f9df685
commit 95b67942cd
22 changed files with 394 additions and 46 deletions

View File

@ -36,7 +36,9 @@
"pages/admin/login",
"pages/admin/exam/add",
"pages/admin/exam/detail",
"pages/admin/exam/index"
"pages/admin/exam/index",
"pages/candidate/show",
"pages/admin/candidate/index"
],
"window": {
"backgroundTextStyle": "light",

BIN
test_mini/images/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,98 @@
// pages/admin/exam/index.js
const api = require("../../../utils/request.js");
Page({
/**
* 页面的初始数据
*/
data: {
page:1,
isLoadedAll:false,
count:0,
results:[]
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.getList(1)
},
getList: function(page) {
wx.showLoading({
title: '正在加载...',
})
api.requesta('/crm/candidate/', 'GET', {page:page}).then(res=>{
wx.hideLoading()
let results = this.data.results
let isLoadedAll = false
if(res.data.results){
if(page==1){results=res.data.results}else{
results = results.concat(res.data.results)
}
}
if(!res.data.next){
isLoadedAll = true
}
this.setData({
count:res.data.count,
results:results,
page:page,
isLoadedAll: isLoadedAll
})
}).catch(
wx.hideLoading()
)
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
this.getList(1)
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
if(!this.data.isLoadedAll){
this.data.page=this.data.page+1
this.getList(this.data.page)
}
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

View File

@ -0,0 +1,5 @@
{
"usingComponents": {},
"navigationBarTitleText": "出征记录",
"enablePullDownRefresh":true
}

View File

@ -0,0 +1,31 @@
<view class="page__bd">
<view class="weui-panel weui-panel_access">
<view class="weui-panel__hd">已发布的证书,共{{count}}张</view>
<view class="weui-panel__bd">
<navigator class="weui-media-box weui-media-box_text" wx:for="{{results}}" wx:key="unique" url="/pages/candidate/detail?id={{item.id}}">
<h4 class="weui-media-box__title">
<text style="color:blue;">{{item.consumer_name}}</text>
{{item.number}}
</h4>
<view class="weui-media-box__desc">
身份证号:{{item.ID_number}}
</view>
<view class="weui-media-box__desc">
工作类别:{{item.workscope_name}}
</view>
<view class="weui-media-box__desc">
期限:{{item.start_date}}至{{item.end_date}}
</view>
</navigator>
</view>
<view class="weui-loadmore weui-loadmore_line" wx:if="{{isLoadedAll}}">
<text class="weui-loadmore__tips">到底了</text>
</view>
<!-- <view class="weui-panel__ft">
<a class="weui-cell weui-cell_active weui-cell_access weui-cell_link">
<view class="weui-cell__bd">查看更多</view>
<text class="weui-cell__ft"></text>
</a>
</view> -->
</view>
</view>

View File

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

View File

@ -102,5 +102,11 @@ Page({
wx.navigateTo({
url: '/pages/test/detail?id='+e.currentTarget.dataset.id,
})
},
goCandidate:function(e){
let candidate_ = e.currentTarget.dataset.candidate_;
wx.navigateTo({
url: '/pages/candidate/show?number='+candidate_.number,
})
}
})

View File

@ -22,7 +22,7 @@
</view>
</view>
<view class="weui-panel weui-panel_access">
<view class="weui-panel__hd">共{{tests.length}}名人员参加考试</view>
<view class="weui-panel__hd">共{{tests.length}}次考试记录</view>
<view class="weui-panel__bd">
<view class="weui-media-box weui-media-box_text" wx:for="{{tests}}" wx:key="unique">
<h4 class="weui-media-box__title">
@ -47,7 +47,7 @@
<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}}" wx:if="{{item.candidate_}}">查看证书</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 File

@ -42,7 +42,7 @@
</view>
<view class="weui-grid__label">考试记录</view>
</navigator>
<navigator class="weui-grid" url="/pages/cuoti/index">
<navigator class="weui-grid" url="/pages/admin/candidate/index">
<view class="weui-grid__icon">
<image src="/images/candidate.svg" alt></image>
</view>

View File

@ -1,4 +1,5 @@
// pages/candidate/detail.js
const api = require("../../utils/request.js");
Page({
/**
@ -12,12 +13,16 @@ Page({
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log(getApp().globalData.candidate_url)
this.setData({
url:getApp().globalData.candidate_url
let id = options.id
this.getDetail(id)
},
getDetail: function(id){
api.request(`/crm/candidate/${id}/`, 'GET').then(res=>{
this.setData({
candidate:res.data
})
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
@ -65,5 +70,18 @@ Page({
*/
onShareAppMessage: function () {
},
godetail: function(e){
wx.navigateTo({
url: '/pages/test/detail?id='+this.data.candidate.examtest,
})
},
goCandidate:function(e){
wx.navigateTo({
url: '/pages/candidate/show?number='+this.data.candidate.number,
})
},
downDoc: function(e){
}
})

View File

@ -1,2 +1,24 @@
<!--pages/candidate/detail.wxml-->
<web-view src="{{url}}"></web-view>
<view style="margin-left:4px;margin-right:4px">
<image style="width: 100%;height:200px" src="/images/bg.png" mode="aspectFit"></image>
</view>
<view class="weui-panel__bd">
<view class="weui-media-box weui-media-box_text" style="text-align:center">
<h4 class="weui-media-box__title">
<text style="color:blue;">{{candidate.consumer_name}}</text>
{{candidate.number}}
</h4>
<view class="weui-media-box__desc">
{{candidate.ID_number}}
</view>
<view class="weui-media-box__desc">
{{candidate.workscope_name}}
</view>
<view class="weui-media-box__desc">
{{candidate.start_date}}至{{candidate.end_date}}
</view>
</view>
</view>
<a class="weui-btn weui-btn_primary" bindtap="goCandidate">查看证书</a>
<a class="weui-btn weui-btn_default" bindtap="godetail">答题信息</a>
<a class="weui-btn weui-btn_default" bindtap="downDoc">下载完整文档</a>

View File

@ -0,0 +1,69 @@
// pages/candidate/show.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
let number = options.number;
this.setData({
url:getApp().globalData.host+'/crm/candidate/img/'+number+'/'
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
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,5 @@
<view class="page-body">
<view class="page-section page-section-gap">
<web-view src="{{url}}"></web-view>
</view>
</view>

View File

@ -0,0 +1 @@
/* pages/candidate/show.wxss */

View File

@ -0,0 +1,21 @@
# Generated by Django 3.0.4 on 2021-06-07 05:44
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('crm', '0033_auto_20210606_2209'),
]
operations = [
migrations.AddField(
model_name='candidate',
name='create_admin',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='创建管理员'),
),
]

View File

@ -120,7 +120,7 @@ class Candidate(CommonModel):
start_date = models.DateField('有效期始', null=True, blank=True)
end_date = models.DateField('有效期止', null=True, blank=True)
examtest = models.OneToOneField(to='examtest.examtest', verbose_name='关联考试', null=True, blank=True, on_delete=models.DO_NOTHING)
create_admin = models.ForeignKey(UserProfile, verbose_name="创建管理员", null=True, blank=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = '证书'
verbose_name_plural = verbose_name

View File

@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import Company, Consumer, PaySubject, ConsumerPerm, ConsumerRole
from .models import Candidate, Company, Consumer, PaySubject, ConsumerPerm, ConsumerRole
from question.models import Question, Questioncat
from .permission import get_consumerperm_list
from rbac.serializers.user_serializer import UserListSerializer
@ -72,4 +72,10 @@ class ConsumerDetailSerializer(serializers.ModelSerializer):
return queryset
def get_perms(self,obj):
return get_consumerperm_list(obj)
return get_consumerperm_list(obj)
class CandidateSerializer(serializers.ModelSerializer):
class Meta:
model = Candidate
fields = '__all__'

View File

@ -21,7 +21,7 @@
justify-content: center;
}
.toolbar {
position: fixed;
top: 0;
@ -120,20 +120,22 @@
draggable="true">
<div class="editable" style="background-color: transparent; word-break: break-all;"
contenteditable="false"><span contenteditable="false" class="inner-cell"
data-tag="name">高彩眉</span><span contenteditable="false" class="inner-cell"
data-tag="gender">女</span><span contenteditable="false" class="inner-cell"
data-tag="birthday">1994年03月04日</span>生,<span contenteditable="false" class="inner-cell"
data-tag="idtype">身份证</span><span contenteditable="false" class="inner-cell"
data-tag="idno">342921199403040528</span>,于<span contenteditable="false" class="inner-cell"
data-tag="examyear">2021</span><span contenteditable="false" class="inner-cell"
data-tag="exammonth">01</span>月参加&nbsp;<span contenteditable="false" class="inner-cell"
data-tag="pworktype">医学其他</span>&nbsp;辐射安全与防护考核,成绩合格。</div>
data-tag="name">{{candidate.consumer_name}}</span>
<!-- <span contenteditable="false" class="inner-cell" data-tag="gender">女</span>
<span contenteditable="false" class="inner-cell" data-tag="birthday">1994年03月04日</span>生, -->
<span contenteditable="false"
class="inner-cell" data-tag="idtype">身份证</span><span contenteditable="false"
class="inner-cell" data-tag="idno">{{candidate.ID_number}}</span>,于
<!-- <span contenteditable="false" class="inner-cell" data-tag="examyear">2021</span>年 -->
<span contenteditable="false" class="inner-cell" data-tag="exammonth">{{candidate.examtest.start_time|date}}</span>参加&nbsp;
<span contenteditable="false"
class="inner-cell" data-tag="pworktype">{{candidate.workscope_name}}</span>&nbsp;辐射安全与防护考核,成绩合格。</div>
</div>
<div id="8cBDPhuQ2bx7BMR7gCEXwo" class="cell moveable " title="自定义文本"
style="position: absolute; width: 63mm; min-height: 5mm; line-height: 2; text-indent: 1em; top: 89.56mm; left: 28.92mm; font-size: 12pt; color: rgb(0, 0, 0);"
draggable="true">
<div class="editable" style="background-color: transparent; word-break: break-all;">编号:<span
contenteditable="false" class="inner-cell" data-tag="certnumber">FS21ZJ0000070</span>&nbsp;
contenteditable="false" class="inner-cell" data-tag="certnumber">{{candidate.number}}</span>&nbsp;
</div>
</div>
<div id="XrsAo57jnLFhfiMUeea7DK" class="cell moveable " title="自定义文本"
@ -141,9 +143,11 @@
draggable="true">
<!-- <div class="editable" style="background-color: transparent; word-break: break-all;"
contenteditable="false">报告单查询网址fushe.mee.gov.cn</div> -->
</div><img id="Q4vEwUqGDkSh5U74XDvaeq" class="cell moveable " src="./报告单_files/qrcode" title="二维码"
</div>
<div id="qrcode" style="position: absolute; width: 25mm; height: 25mm; top: 101.48mm; left: 152.69mm;"></div>
<!-- <img id="qrcode" class="cell moveable " src="./报告单_files/qrcode" title="二维码"
alt="二维码" style="position: absolute; width: 25mm; height: 25mm; top: 101.48mm; left: 152.69mm;"
draggable="true">
draggable="true"> -->
<div id="AkTVQPAB5skpWAwyV5QGPm" class="cell moveable " title="自定义文本"
style="position: absolute; width: 18mm; min-height: 5mm; line-height: 1.5; text-indent: 0em; text-decoration: unset; font-style: normal; color: rgb(0, 0, 0); top: 90.56mm; left: 91.83mm; font-size: 12pt;"
draggable="true">
@ -154,30 +158,35 @@
draggable="true">
<div class="editable" style="background-color: transparent; word-break: break-all;"></div>
</div><span id="MAAR3FWxqXrieXZQTwJ9PJ" class="cell moveable " title="有效截止日期" dtype="1" draggable="true"
style="position: absolute; width: 34mm; text-decoration: unset; font-style: normal; color: rgb(0, 0, 0); top: 90.56mm; left: 146.09mm; font-size: 12pt;"><span
data-tag="enddate">2026年01月27日</span></span><span id="N9LxjQaAG4TXvjiHd9nwm7"
style="position: absolute; width: 34mm; text-decoration: unset; font-style: normal; color: rgb(0, 0, 0); top: 91mm; left: 146.09mm; font-size: 12pt;"><span
data-tag="enddate">{{candidate.end_date}}</span></span><span id="N9LxjQaAG4TXvjiHd9nwm7"
class="cell moveable " title="发证日期" dtype="1" draggable="true"
style="position: absolute; width: 32mm; text-decoration: unset; font-style: normal; color: rgb(0, 0, 0); top: 90.56mm; left: 107.7mm; font-size: 12pt;"><span
data-tag="certdate">2021年01月27日</span></span>
style="position: absolute; width: 32mm; text-decoration: unset; font-style: normal; color: rgb(0, 0, 0); top: 91mm; left: 107.7mm; font-size: 12pt;"><span
data-tag="certdate">{{candidate.start_date}}</span></span>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="text/javascript">
// $(document).ready(function() {
// });
console.log(window.location.href)
new QRCode(document.getElementById("qrcode"), {
text: window.location.href,
width: 100,
height: 100});
function init() {
// $('select').change(function() {
// $('form').submit();
// });
if (navigator.userAgent.match(/mobile/i)) {
$('#print_area').hide();
// $('#print_area').hide();
document.querySelector('#print_area').style.display = 'none';
}
}

View File

@ -1,6 +1,6 @@
from django.urls import path,include
from .views import CompanyViewSet, \
ConsumerViewSet, ConsumerMPLoginView, ConsumerRegister, change_remain_count, ConsumerPermViewSet, ConsumerRoleViewSet, candidate
ConsumerViewSet, ConsumerMPLoginView, ConsumerRegister, change_remain_count, ConsumerPermViewSet, ConsumerRoleViewSet, candidate, CandidateViewSet
from rest_framework import routers
@ -9,11 +9,11 @@ router.register('consumer', ConsumerViewSet, basename="consumer")
router.register('company', CompanyViewSet, basename="company")
router.register('consumerrole', ConsumerRoleViewSet, basename="consumerrole")
router.register('consumerperm', ConsumerPermViewSet, basename="consumerperm")
router.register('candidate', CandidateViewSet, basename="candidate")
urlpatterns = [
path('consumer/mplogin/', ConsumerMPLoginView.as_view()),
path('consumer/register/', ConsumerRegister.as_view()),
path('change_remain_count/', change_remain_count.as_view()),
path('candidate/<str:number>/',candidate),
path('candidate/img/<str:number>/',candidate),
path('', include(router.urls)),
]

View File

@ -11,10 +11,11 @@ from openpyxl import Workbook, load_workbook
from rest_framework import status
from rest_framework.decorators import action, authentication_classes, permission_classes
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.serializers import jwt_encode_handler
from rest_framework_jwt.settings import api_settings
@ -27,8 +28,8 @@ from server import settings
from utils.custom import CommonPagination
from .filters import ConsumerFilter
from .exports import export_consumer
from .models import Company, Consumer, PaySubject, SendCode, ConsumerPerm, ConsumerRole
from .serializers import CompanySerializer, ConsumerSerializer, ConsumerPermSerializer, ConsumerRoleSerializer, ConsumerDetailSerializer
from .models import Candidate, Company, Consumer, PaySubject, SendCode, ConsumerPerm, ConsumerRole
from .serializers import CandidateSerializer, CompanySerializer, ConsumerSerializer, ConsumerPermSerializer, ConsumerRoleSerializer, ConsumerDetailSerializer
from lxml import etree
from rbac.models import UserProfile
from django.http import Http404
@ -647,4 +648,52 @@ class change_remain_count(APIView):
from django.shortcuts import render
def candidate(request, number):
return render(request, 'index.html')
candidate = Candidate.objects.get(number=number)
return render(request, 'index.html', {"candidate":candidate})
class CandidateViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet):
"""
出征记录:列表
"""
perms_map = [
{'get': '*'}, {'post': 'candidate_issue'},
{'put': 'candidate_update'}, {'delete': 'candidate_delete'}]
queryset = Candidate.objects.filter(number__isnull=False)
serializer_class = CandidateSerializer
pagination_class = CommonPagination
filter_backends = [DjangoFilterBackend,SearchFilter, OrderingFilter]
search_fields = ('number', 'consumer_name', 'workscope_name', 'company_name')
ordering_fields = ('-id', 'update_time')
ordering = ['-update_time']
def get_authenticators(self, request):
if self.request.action == 'retrieve':
return []
return super().get_authenticators()
def get_permissions(self):
if self.action == 'retrieve':
return []
return super().get_permissions()
def retrieve(self, request, *args, **kwargs):
instance = self.queryset.get(*args, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
def get_queryset(self):
queryset = self.queryset
if self.request.user.is_superuser:
return queryset
roles = self.request.user.roles.values_list('name', flat=True)
if '普通管理' in roles:
queryset = queryset.filter(create_admin = self.request.user)
elif '省管理' in roles:
if self.request.user.pname:
queryset = queryset.filter(consumer__company__geo__pname = self.request.user.pname)
else:
return Candidate.objects.none()
else:
return queryset.none()
print(queryset)
return queryset

View File

@ -318,10 +318,11 @@ class WorkScopeViewSet(ModelViewSet):
"""
obj = self.get_object()
if obj.can_exam:
try:
Candidate.objects.get_or_create(consumer=request.user, workscope=request.user.workscope, defaults={'consumer':request.user, 'workscope':request.user.workscope})
except:
candidates = Candidate.objects.filter(consumer=request.user, workscope=request.user.workscope)
if candidates.exists():
pass
else:
Candidate.objects.create(consumer=request.user, workscope=request.user.workscope)
request.user.workscope = obj
request.user.save()
return Response({'workscope':obj.pk, 'workscope_name':obj.name})
@ -385,7 +386,7 @@ class ExamTestViewSet(PageOrNot, ModelViewSet):
queryset = ExamTest.objects.filter(is_delete=0).all()
serializer_class = ExamTestExamListSerializer
ordering_fields = ('id','create_time','took','score')
ordering = ['-create_time']
ordering = ['-create_time', 'is_pass', '-score']
search_fields = ('consumer__name', 'consumer__company__name')
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['type','is_pass', 'exam']
@ -505,8 +506,8 @@ class ExamTestViewSet(PageOrNot, ModelViewSet):
'''
obj = self.get_object()
candidate = obj.candidate if hasattr(obj, 'candidate') else None
# if not obj.is_pass:
# return Response({'error':'考试未通过'})
if not obj.is_pass:
return Response({'error':'考试未通过'})
if candidate:
return Response({'error':'证书已存在'})
candidates = Candidate.objects.filter(consumer=obj.consumer, workscope=obj.workscope, number__isnull=True)
@ -514,18 +515,19 @@ class ExamTestViewSet(PageOrNot, ModelViewSet):
candidate = candidates[0]
else:
candidate = Candidate.objects.create(consumer=obj.consumer, workscope=obj.workscope)
count = Candidate.objects.exclude(number__isnull=True).count()
candidate.examtest = obj
now = timezone.now()
candidate.issue_date = now
candidate.start_date = now
candidate.end_date = now + timedelta(days=3*365)
candidate.end_date = now + timedelta(days=5*365) # 5年有效期
candidate.workscope_name = obj.workscope.name
candidate.consumer_name = obj.consumer_detail['name']
candidate.ID_number = obj.consumer_detail['ID_number']
candidate.company_name = obj.consumer_detail['company_name']
candidate.deptname = obj.consumer_detail['deptname']
candidate.number='SL'+ str(now.year)[-2:] + str(count+1).zfill(6)
candidate.save()
candidate.number='SL'+ str(now.year)[-2:] + str(candidate.pk).zfill(6)
candidate.create_admin = request.user
candidate.save()
return Response({"id":candidate.pk, "number":candidate.number})