瀏覽代碼

Merge remote-tracking branch 'origin/master'

wushaodong 2 年之前
父節點
當前提交
55495e4863
共有 57 個文件被更改,包括 596 次插入145 次删除
  1. 6 0
      apps/api/admin/exam/views.py
  2. 2 0
      apps/api/admin/urls.py
  3. 24 0
      apps/api/admin/user/views.py
  4. 15 0
      apps/api/admin/views.py
  5. 11 2
      apps/api/staff/exam/views.py
  6. 11 2
      apps/api/staff/mock/views.py
  7. 11 2
      apps/api/staff/practise/views.py
  8. 2 0
      apps/api/staff/urls.py
  9. 7 0
      apps/api/staff/views.py
  10. 16 1
      apps/staff/filters.py
  11. 20 0
      apps/staff/models.py
  12. 19 0
      apps/staff/serializers.py
  13. 0 0
      licence
  14. 3 1
      requirements
  15. 20 0
      uis/admin/examlog/index.html
  16. 3 3
      uis/admin/user/edit.html
  17. 84 5
      uis/admin/user/index.html
  18. 二進制
      uis/dist/img/log.png
  19. 6 4
      uis/layuiadmin/modules/common.js
  20. 63 0
      utils/empower.py
  21. 7 0
      utils/permission.py
  22. 0 23
      vue/.gitignore
  23. 5 0
      vue/README.md
  24. 二進制
      vue/dist.zip
  25. 1 1
      vue/package.json
  26. 3 6
      vue/src/App.vue
  27. 4 0
      vue/src/api/global.js
  28. 二進制
      vue/src/assets/home/1.png
  29. 二進制
      vue/src/assets/login/1.png
  30. 二進制
      vue/src/assets/login/2.jpg
  31. 二進制
      vue/src/assets/login/微信图片_20211231091244.png
  32. 二進制
      vue/src/assets/logo.png
  33. 124 21
      vue/src/components/Basic/index.vue
  34. 0 12
      vue/src/components/TestPaper/components/bool.vue
  35. 1 12
      vue/src/components/TestPaper/components/checkbox.vue
  36. 0 12
      vue/src/components/TestPaper/components/input.vue
  37. 0 12
      vue/src/components/TestPaper/components/radio.vue
  38. 9 8
      vue/src/components/TestPaper/index.vue
  39. 4 2
      vue/src/components/WTestPaper/components/bool.vue
  40. 4 2
      vue/src/components/WTestPaper/components/checkbox.vue
  41. 4 2
      vue/src/components/WTestPaper/components/input.vue
  42. 4 2
      vue/src/components/WTestPaper/components/radio.vue
  43. 二進制
      vue/src/utils/font_2724689_mtterbh58wm.ttf
  44. 二進制
      vue/src/utils/font_2724689_mtterbh58wm.woff
  45. 二進制
      vue/src/utils/font_2724689_mtterbh58wm.woff2
  46. 2 1
      vue/src/views/Exam/formal_exam.vue
  47. 2 1
      vue/src/views/Exam/index.vue
  48. 3 1
      vue/src/views/Exam/mock_exam.vue
  49. 2 2
      vue/src/views/Home/pages/components/P_tab_a.vue
  50. 0 1
      vue/src/views/Home/pages/formal_examination.vue
  51. 8 4
      vue/src/views/Home/pages/knowledge_base.vue
  52. 7 0
      vue/src/views/Login/index.vue
  53. 1 0
      生成授权码/key
  54. 1 0
      生成授权码/licence
  55. 36 0
      生成授权码/sign.py
  56. 1 0
      获取机器码/key
  57. 40 0
      获取机器码/mac.py

+ 6 - 0
apps/api/admin/exam/views.py

@@ -125,3 +125,9 @@ class ExamLogViewSet(ReadOnlyModelViewSet):
     def answer_log(self, request, pk):
         rows = ExamAnswerLog.objects.filter(main_id=pk).order_by('detail__order').values_list('status', flat=True)
         return response_ok(list(rows))
+
+    @action(methods=['get'], detail=False)
+    def export(self, request):
+        queryset = self.filter_queryset(self.queryset)
+        serializer = self.get_serializer(queryset, many=True)
+        return response_ok(serializer.data)

+ 2 - 0
apps/api/admin/urls.py

@@ -10,6 +10,8 @@ urlpatterns = [
     url(r'^token_refresh/$', AdminUserRefreshTokenView.as_view()),
     url(r'^token_verify/$', AdminUserVerifyTokenView.as_view()),
 
+    url(r'^logout/$', LogoutView.as_view()),
+
     url(r'^department/', include('apps.api.admin.department.urls')),
     url(r'^user/', include('apps.api.admin.user.urls')),
     url(r'^subject/', include('apps.api.admin.subject.urls')),

+ 24 - 0
apps/api/admin/user/views.py

@@ -4,6 +4,7 @@ import json
 
 from django.db import transaction
 from django.contrib.auth import get_user_model
+from rest_framework.decorators import action
 from utils.permission import IsAdministrator
 from utils import response_error, response_ok
 from utils.custom_modelviewset import CustomModelViewSet
@@ -12,6 +13,8 @@ from rest_framework.views import APIView
 from apps.staff.serializers import UserSerializer
 from apps.staff.filters import UserFilter
 from apps.system.models import SysLog
+from apps.examination.exam.models import ExamLog
+
 User = get_user_model()
 
 class UserViewSet(CustomModelViewSet):
@@ -35,6 +38,27 @@ class UserViewSet(CustomModelViewSet):
         validated_data = serializer.validated_data
         SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改账户[%s],id=%d' % (instance.username, instance.id), validated_data)
 
+    def destroy(self, request, *args, **kwargs):
+        with transaction.atomic():
+            instance = self.get_object()
+            log_count = SysLog.objects.filter(user_id=instance.id).count()
+            exam_log_count = ExamLog.objects.filter(user_id=instance.id).count()
+            if log_count > 0 or exam_log_count > 0:
+                raise CustomError(u'该账号有使用记录,禁止删除!')
+
+            SysLog.objects.addnew(self.request.user, SysLog.DELETE, u'删除用户[%s],id=%d' % (instance.username, instance.id))
+            instance.delete()
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def reset_password(self, request, pk):
+        with transaction.atomic():
+            instance = self.get_object()
+            instance.set_password('111111')
+            instance.save()
+
+            SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'重置用户[%s]密码,id=%d' % (instance.username, instance.id))
+        return response_ok()
 
 class ChangePasswordView(APIView):
     permission_classes = [IsAdministrator, ]

+ 15 - 0
apps/api/admin/views.py

@@ -3,8 +3,12 @@
 from django.contrib.auth import get_user_model
 from rest_framework_jwt.views import ObtainJSONWebToken, VerifyJSONWebToken, RefreshJSONWebToken
 from rest_framework.serializers import ValidationError
+from rest_framework.views import APIView
 from utils import response_error, response_ok
+from utils.permission import IsAdministrator
+from utils.empower import checkLicence
 from apps.staff.serializers import AdminUserJWTSerializer
+from utils.exceptions import CustomError
 
 User = get_user_model()
 
@@ -12,6 +16,10 @@ class AdminUserLoginView(ObtainJSONWebToken):
     serializer_class = AdminUserJWTSerializer
 
     def post(self, request, *args, **kwargs):
+
+        #if not checkLicence():
+        #    raise CustomError(u'授权失败!')
+
         try:
             ser = self.serializer_class(data=request.data)
             ser.request = request
@@ -39,3 +47,10 @@ class AdminUserRefreshTokenView(RefreshJSONWebToken):
                 return response_ok({'token': ser.validated_data['token']})
         except ValidationError as e:
             return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')
+
+class LogoutView(APIView):
+    permission_classes = [IsAdministrator, ]
+
+    def get(self, request):
+        User.objects.filter(pk=request.user.pk).update(status=User.OFFLINE)
+        return response_ok()

+ 11 - 2
apps/api/staff/exam/views.py

@@ -128,7 +128,16 @@ class ExamLogViewSet(CustomModelViewSet):
                     if not detail:
                         raise CustomError('提交的考试习题有误,请刷新重试!')
                     now_question = detail.question
-                    if len(answers) > 0:
+
+                    has_answers = len(answers) > 0
+                    if now_question.type == ExamQuestion.FILL:
+                        has_answers = False
+                        for a in range(0, len(answers)):
+                            if answers[a]:
+                                has_answers = True
+                                break
+
+                    if has_answers > 0:
                         answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance,
                                                                                  detail=detail, )
                         if now_question.type <= ExamQuestion.MULTIPLE:
@@ -142,7 +151,7 @@ class ExamLogViewSet(CustomModelViewSet):
                             answers_len = len(answers)
                             ExamAnswerFillLog.objects.filter(main=answer_log).delete()
                             for a in range(0, answers_len):
-                                ExamAnswerFillLog.objects.create(main=answer_log, content=answers[a], order=a + 1)
+                                ExamAnswerFillLog.objects.create(main=answer_log, content=answers[a] or '', order=a + 1)
                         else:
                             # 判断
                             if answers[0] == 1:

+ 11 - 2
apps/api/staff/mock/views.py

@@ -119,7 +119,16 @@ class ExamLogViewSet(CustomModelViewSet):
                     if not detail:
                         raise CustomError('提交的考试习题有误,请刷新重试!')
                     now_question = detail.question
-                    if len(answers) > 0:
+
+                    has_answers = len(answers) > 0
+                    if now_question.type == ExamQuestion.FILL:
+                        has_answers = False
+                        for a in range(0, len(answers)):
+                            if answers[a]:
+                                has_answers = True
+                                break
+
+                    if has_answers > 0:
                         answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance,
                                                                                  detail=detail, )
                         if now_question.type <= ExamQuestion.MULTIPLE:
@@ -133,7 +142,7 @@ class ExamLogViewSet(CustomModelViewSet):
                             answers_len = len(answers)
                             ExamAnswerFillLog.objects.filter(main=answer_log).delete()
                             for a in range(0, answers_len):
-                                ExamAnswerFillLog.objects.create(main=answer_log, content=answers[a], order=a + 1)
+                                ExamAnswerFillLog.objects.create(main=answer_log, content=answers[a] or '', order=a + 1)
                         else:
                             # 判断
                             if answers[0] == 1:

+ 11 - 2
apps/api/staff/practise/views.py

@@ -71,7 +71,16 @@ class PractiseLogViewSet(CustomModelViewSet):
                     now_question = ExamQuestion.objects.filter(id=now_practise).first()
                     if not now_question:
                         raise CustomError('提交的习题有误,请刷新重试!')
-                    if len(answers) > 0:
+
+                    has_answers = len(answers) > 0
+                    if now_question.type == ExamQuestion.FILL:
+                        has_answers = False
+                        for a in range(0, len(answers)):
+                            if answers[a]:
+                                has_answers = True
+                                break
+
+                    if has_answers > 0:
                         answer_log, create = PractiseAnswerLog.objects.get_or_create(main=instance,
                                                                                      question=now_question, )
                         if now_question.type == ExamQuestion.SINGLE:
@@ -104,7 +113,7 @@ class PractiseLogViewSet(CustomModelViewSet):
                             right = 1
                             PractiseAnswerFillLog.objects.filter(main=answer_log).delete()
                             for a in range(0, answers_len):
-                                PractiseAnswerFillLog.objects.create(main=answer_log, content=answers[a], order=a + 1)
+                                PractiseAnswerFillLog.objects.create(main=answer_log, content=answers[a] or '', order=a + 1)
                                 right_answer = ExamQuestionFill.objects.filter(main=now_question, content=answers[a],
                                                                                order=a + 1)
                                 if not right_answer:

+ 2 - 0
apps/api/staff/urls.py

@@ -11,6 +11,8 @@ urlpatterns = [
     url(r'^token_verify/$', StaffUserVerifyTokenView.as_view()),
     url(r'^change_password/$', ChangePasswordView.as_view()),
 
+    url(r'^logout/$', LogoutView.as_view()),
+
     url(r'^practise/', include('apps.api.staff.practise.urls')),
     url(r'^mock/', include('apps.api.staff.mock.urls')),
     url(r'^errorbook/', include('apps.api.staff.errorbook.urls')),

+ 7 - 0
apps/api/staff/views.py

@@ -59,3 +59,10 @@ class ChangePasswordView(APIView):
             request.user.save()
             SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改账户密码')
         return response_ok()
+
+class LogoutView(APIView):
+    permission_classes = [IsStaff, ]
+
+    def get(self, request):
+        User.objects.filter(pk=request.user.pk).update(status=User.OFFLINE)
+        return response_ok()

+ 16 - 1
apps/staff/filters.py

@@ -1,18 +1,33 @@
 # coding=utf-8
 import django_filters
 
+import datetime
+from django.utils import timezone
+from django.db.models import Q
 from django.contrib.auth import get_user_model
+
 from .models import Department
 
 User = get_user_model()
 
 class UserFilter(django_filters.FilterSet):
     username = django_filters.CharFilter(field_name='username', lookup_expr='icontains')
+    name = django_filters.CharFilter(field_name='name', lookup_expr='icontains')
     is_active = django_filters.CharFilter(field_name='is_active')
+    department = django_filters.CharFilter(field_name='department_id')
+    online = django_filters.CharFilter(method='online_filter')
 
     class Meta:
         model = User
-        fields = ['username', 'is_active', 'type', ]
+        fields = ['username', 'name', 'is_active', 'type', ]
+
+    def online_filter(self, queryset, name,value):
+        start = timezone.now() - datetime.timedelta(hours=0, minutes=10, seconds=0)
+        if value == '0':
+            queryset = queryset.filter( Q(status__isnull=True) | Q(status=User.OFFLINE)| Q(last_refresh__lt=start) )
+        elif value == '1':
+            queryset = queryset.filter(status=User.ONLINE, last_refresh__gt=start)
+        return queryset
 
 class DepartmentFilter(django_filters.FilterSet):
     name = django_filters.CharFilter(field_name='name', lookup_expr='icontains')

+ 20 - 0
apps/staff/models.py

@@ -135,6 +135,13 @@ class User(AbstractBaseUser, PermissionsMixin):
     ADMINSTRATOR = 1
     STAFF = 2
 
+    OFFLINE = 0
+    ONLINE = 1
+    STATUS_CHOICES = (
+        (OFFLINE, u'离线'),
+        (ONLINE, u'在线'),
+    )
+
     type = models.PositiveSmallIntegerField(verbose_name=u"类型")
     department = models.ForeignKey(Department, verbose_name=u"所属部门", null=True, blank=True, on_delete=models.PROTECT)
     username = models.CharField(verbose_name=u'帐号', max_length=30, unique=True, db_index=True)
@@ -142,6 +149,9 @@ class User(AbstractBaseUser, PermissionsMixin):
     is_active = models.BooleanField(verbose_name=u'激活', default=True)
     date_joined = models.DateTimeField(verbose_name=u'注册时间', default=timezone.now, editable=False)
 
+    last_refresh = models.DateTimeField(verbose_name=u'刷新时间', null=True, editable=False)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"状态", default=OFFLINE)
+
     objects = UserManager()
 
     USERNAME_FIELD = 'username'
@@ -189,6 +199,11 @@ class User(AbstractBaseUser, PermissionsMixin):
             raise CustomError(u'两次输入的密码不一致, 请检查')
         if not self.check_password(old_password):
             raise CustomError(u'原密码输入错误, 请检查')
+        if len(new_password) < 6:
+            raise CustomError(u'密码长度不能少于6位字符!')
+        if new_password == self.username:
+            raise CustomError(u'密码不能和用户名相同!')
+
         self.set_password(new_password)
 
     def update_item(self, validated_data):
@@ -211,6 +226,11 @@ class User(AbstractBaseUser, PermissionsMixin):
             validated_data['password'] = self.password
             update()
         else:
+            if len(validated_data['password']) < 6:
+                raise CustomError(u'密码长度不能少于6位字符!')
+            if validated_data['password'] == validated_data['username']:
+                raise CustomError(u'密码不能和用户名相同!')
+
             update()
             self.set_password(validated_data['password'])
         self.save()

+ 19 - 0
apps/staff/serializers.py

@@ -1,4 +1,7 @@
 # coding=utf-8
+
+import datetime
+from django.utils import timezone
 from django.contrib.auth import get_user_model, authenticate
 from django.db.models import F
 
@@ -37,6 +40,8 @@ class AdminUserJWTSerializer(JSONWebTokenSerializer):
                     SysLog.objects.addnew(user, SysLog.INSERT,u'非管理员账号[%s]尝试登录管理系统,IP[%s]' % (user.username, get_remote_addr(self.request)))
                     raise serializers.ValidationError(msg)
 
+                User.objects.filter(pk=user.pk).update(status=User.ONLINE)
+
                 payload = jwt_payload_handler(user)
                 SysLog.objects.addnew(user, SysLog.INSERT, u'[%s]登录管理系统,IP[%s]' % (user.username,get_remote_addr(self.request)))
 
@@ -76,6 +81,8 @@ class StaffUserJWTSerializer(JSONWebTokenSerializer):
                     SysLog.objects.addnew(user, SysLog.INSERT,u'非工作账号[%s]尝试登录答题系统,IP[%s]' % (user.username, get_remote_addr(self.request)))
                     raise serializers.ValidationError(msg)
 
+                User.objects.filter(pk=user.pk).update(status=User.ONLINE)
+
                 payload = jwt_payload_handler(user)
                 SysLog.objects.addnew(user, SysLog.INSERT, u'[%s]登录答题系统,IP[%s]' % (user.username,get_remote_addr(self.request)))
 
@@ -143,6 +150,7 @@ class UserSerializer(serializers.ModelSerializer):
     status_text = serializers.SerializerMethodField()
     department_text = serializers.CharField(source='department.name', read_only=True)
     type_text = serializers.SerializerMethodField()
+    online_text = serializers.SerializerMethodField()
 
     class Meta:
         model = User
@@ -160,9 +168,20 @@ class UserSerializer(serializers.ModelSerializer):
             return u'普通'
         return ''
 
+    def get_online_text(self, obj):
+        start = timezone.now() - datetime.timedelta(hours=0, minutes=10, seconds=0)
+        if obj.status == User.ONLINE and obj.last_refresh > start:
+            return '是'
+        return '否'
+
     def create(self, validated_data):
         if validated_data['password'].strip() == '':
             raise CustomError(u'密码不能为空!')
+        if len(validated_data['password']) < 6:
+            raise CustomError(u'密码长度不能少于6位字符!')
+        if validated_data['password'] == validated_data['username']:
+            raise CustomError(u'密码不能和用户名相同!')
+
         if 'type' in validated_data and validated_data['type'] == User.STAFF:
             user = User.objects.create_staff(validated_data['username'], validated_data['password'], name=validated_data['name'], is_active=validated_data['is_active'], department=validated_data['department'])
         else:

+ 0 - 0
licence


+ 3 - 1
requirements

@@ -4,4 +4,6 @@ djangorestframework
 djangorestframework-jwt
 django-cors-headers
 tablib==3.1.0
-openpyxl==3.0.9
+openpyxl==3.0.9
+cx-Oracle
+pycryptodome

+ 20 - 0
uis/admin/examlog/index.html

@@ -63,6 +63,9 @@
             <div class="layui-row layui-col-space15">
                 <div class="layui-col-md12">
                     <div class="LAY-btns" style="margin-bottom: 10px;">
+                        <div style="float: left">
+                            <button class="layui-btn" id="btn_download"><i class="layui-icon layui-icon-download-circle"></i>导出</button>
+                        </div>
                         <form class="layui-form" lay-filter="query-form-element">
                             <div class="seach_items">
                                 <button class="layui-btn" lay-submit lay-filter="query-form-element"><i
@@ -107,6 +110,8 @@
 
 <script src="../../layuiadmin/layui/layui.js"></script>
 <script>
+    var _params = '';
+
     layui.config({
         base: '../../../layuiadmin/' //静态资源所在路径
     }).extend({
@@ -134,6 +139,8 @@
         table.render({
             elem: '#exampaper_datagrid'
             , url: '/admin/exam/examlog/'
+            ,title: '考试记录'
+            ,id: 'exampaper_datagrid'
             , cols: [[
                 {title: '编号', type: 'numbers'}
                 , {field: 'exam_name', title: '考试名称', width: 200}
@@ -177,9 +184,22 @@
                 where: data.field
                 , page: {curr: 1}
             });
+            _params = data.field;
             layer.closeAll();
             return false
         });
+
+        $('#btn_download').on('click', function(){
+        $.get({
+            url: '/admin/exam/examlog/export/',
+            dataType: 'json',
+            data: _params,
+            success: function (res) {
+                table.exportFile('exampaper_datagrid', res.data, 'xls')
+            }
+        })
+    });
+
     });
 
 </script>

+ 3 - 3
uis/admin/user/edit.html

@@ -41,7 +41,7 @@
                 </div>
 
                   <div class="layui-col-lg6">
-                  <label class="layui-form-label"><font color='red' size="4">*</font>账号:</label>
+                  <label class="layui-form-label"><font color='red' size="4">*</font>用户名:</label>
                   <div class="layui-input-block">
                     <input type="text" name="username" lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
                   </div>
@@ -51,7 +51,7 @@
                     <label class="layui-form-label"><font color='red' size="4">*</font>密码:</label>
                     <div class="layui-input-block">
                       <input type="password" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
-                        <div class="layui-word-aux">默认密码:1111;<br>修改信息时如果留空,则不修改密码。</div>
+                        <div class="layui-word-aux">默认密码:111111;<br>修改信息时如果留空,则不修改密码。</div>
                     </div>
                   </div>
 
@@ -120,7 +120,7 @@
         type = 'post';
     }
 
-    form.val("component-form-element", {'password':'1111'});
+    form.val("component-form-element", {'password':'111111'});
     if(editdata){
         form.val("component-form-element", editdata);
     }

+ 84 - 5
uis/admin/user/index.html

@@ -8,6 +8,10 @@
   <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
   <link rel="stylesheet" href="../../layuiadmin/layui/css/layui.css" media="all">
   <link rel="stylesheet" href="../../layuiadmin/style/admin.css" media="all">
+    <link rel="stylesheet" type="text/css" href="../../layuiadmin/style/formSelects-v4.css"/>
+    <style type="text/css">
+        #department_selecter dl{max-height: 250px;}
+    </style>
 </head>
 <body>
 
@@ -28,6 +32,12 @@
                 <div class="layui-btn-group">
                   <a class="layui-btn layui-btn-xs" lay-event="edit">修改</a>
                 </div>
+                <div class="layui-btn-group">
+                    <a class="layui-btn layui-btn-xs" lay-event="user_delete">删除</a>
+                </div>
+                <div class="layui-btn-group">
+                    <a class="layui-btn layui-btn-xs" lay-event="reset_password">重置密码</a>
+                </div>
             </script>
           </div>
         </div>
@@ -49,6 +59,18 @@
                     </select>
                 </div>
             </div>
+
+              <div class="layui-col-xs12 layui-col-sm12">
+                <label class="layui-form-label">在线:</label>
+                <div class="layui-input-block">
+                    <select  name="online">
+                        <option value="" selected></option>
+                        <option value="1">是</option>
+                        <option value="0">否</option>
+                    </select>
+                </div>
+            </div>
+
               <div class="layui-col-xs12 layui-col-sm12">
                 <label class="layui-form-label">类型:</label>
                 <div class="layui-input-block">
@@ -60,13 +82,27 @@
                 </div>
             </div>
 
+               <div class="layui-col-xs12 layui-col-sm12">
+                <label class="layui-form-label">部门:</label>
+                <div class="layui-input-block" id="department_selecter">
+                    <select name="department" xm-select="selectDepartment" xm-select-radio></select>
+                </div>
+            </div>
+
             <div class="layui-col-xs12 layui-col-sm12">
-                <label class="layui-form-label">登录账号:</label>
+                <label class="layui-form-label">用户名:</label>
                 <div class="layui-input-block">
                     <input type="text" name="username" autocomplete="off" class="layui-input">
                 </div>
             </div>
 
+              <div class="layui-col-xs12 layui-col-sm12">
+                <label class="layui-form-label">姓名:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="name" autocomplete="off" class="layui-input">
+                </div>
+            </div>
+
 
           </div>
           <div class="layui-form-item" style="display: none">
@@ -83,21 +119,25 @@
     base: '../../../layuiadmin/' //静态资源所在路径
   }).extend({
     index: 'lib/index' //主入口模块
-  }).use(['index', 'table'], function(){
+    ,formSelects: 'formSelects-v4'
+  }).use(['index', 'table', 'formSelects'], function(){
     var $ = layui.$
     ,form = layui.form;
     var table = layui.table;
+      var admin = layui.admin;
+      var dep_formSelects = layui.formSelects;
 
     table.render({
       elem: '#datagrid'
       ,url: '/admin/user/'
       ,cols: [[
         {field:'name', title:'姓名',width: 100}
-        ,{field:'username', title:'登录账号',width: 200}
+        ,{field:'username', title:'用户名',width: 200}
         ,{field:'department_text', title:'所属部门', width:300}
         ,{field:'type_text', title:'类型', width:90}
         ,{field:'status_text', title:'启用', width:90}
-        ,{width:80, align:'left',title: '操作', fixed: 'right', toolbar: '#datagrid-operate-bar'}
+        ,{field:'online_text', title:'在线', width:90}
+        ,{width:200, align:'left',title: '操作', fixed: 'right', toolbar: '#datagrid-operate-bar'}
       ]]
       ,page: true
       ,height: 'full-104'
@@ -130,7 +170,29 @@
           },
           content: 'edit.html?id='+data.id
         });
-      }
+        }else if (obj.event === 'user_delete') {
+            layer.confirm('确定要删除该用户吗?', function (index) {
+                layer.close(index);
+                admin.req({
+                    url: '/admin/user/' + data.id + '/'
+                    , type: 'delete'
+                    , done: function (res) {
+                        table.reload('datagrid', {});
+                    }
+                });
+            });
+        } else if (obj.event === 'reset_password') {
+            layer.confirm('确定要重置该用户密码吗?', function (index) {
+                layer.close(index);
+                admin.req({
+                    url: '/admin/user/' + data.id + '/reset_password/'
+                    , type: 'post'
+                    , done: function (res) {
+                        table.reload('datagrid', {});
+                    }
+                });
+            });
+        }
     });
 
     $('#btn_add').on('click', function(){
@@ -179,6 +241,23 @@
       return false;
     });
 
+      dep_formSelects.value('selectDepartment', []);
+    admin.req({
+        url: '/admin/department/tree/'
+        ,done: function(res){
+            dep_formSelects.data('selectDepartment', 'local', {
+            arr: res.data,
+            tree: {
+                //在点击节点的时候, 如果没有子级数据, 会触发此事件
+                nextClick: function(id, item, callback){
+                    return false;
+                    },
+                }
+            });
+            dep_formSelects.value('selectDepartment', [department]);
+        }
+    });
+
   });
   </script>
 </body>

二進制
uis/dist/img/log.png


+ 6 - 4
uis/layuiadmin/modules/common.js

@@ -23,23 +23,25 @@ layui.define(function(exports){
   //退出
   admin.events.logout = function(){
     //执行退出接口
-      /*
+      
     admin.req({
-      url: '/account/logout/'
+      url: '/admin/logout/'
       ,type: 'get'
       ,data: {}
       ,done: function(res){ //这里要说明一下:done 是只有 response 的 code 正常才会执行。而 succese 则是只要 http 为 200 就会执行
 
         //清空本地记录的 token,并跳转到登入页
         admin.exit(function(){
-          location.href = '/views/account/login.html';
+          location.href = '/admin/login/login.html';
         });
       }
     });
-    */
+    
+	/*
     admin.exit(function(){
         location.href = '/admin/login/login.html';
       });
+	*/
   };
 
 

+ 63 - 0
utils/empower.py

@@ -0,0 +1,63 @@
+#coding=utf-8
+import Crypto.Hash
+import Crypto.PublicKey.RSA
+import Crypto.Signature.PKCS1_v1_5
+
+import uuid
+import platform
+import base64
+import hashlib
+import os
+
+XorKey = [0xB2, 0x09, 0xBB, 0x55, 0x93, 0x83, 0x03, 0x24]
+
+def enc(src):
+    j, result = 0, ""
+    for s in src:
+        result = result + hex(ord(s) ^ (XorKey[j]))[2:]
+        j = (j + 1) % 8
+    return result
+
+def getMac():
+    sysstr = platform.system()
+    if (sysstr == "Windows"):
+        import winreg
+        key = winreg.OpenKey(
+            winreg.HKEY_LOCAL_MACHINE,
+            "SOFTWARE\\Microsoft\\Cryptography",
+            0,
+            winreg.KEY_READ | winreg.KEY_WOW64_64KEY
+        )
+        result = winreg.QueryValueEx(key, "MachineGuid")
+        mac = result[0]
+    else:
+        mac = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0, 8 * 6, 8)][::-1])
+
+    path = '/auth/kaohe/v2/'
+    mac += 'ZZLY[' + path + ']20190325'
+    mac = enc(mac)
+    mac = base64.b64encode(mac.encode())
+    mac = hashlib.sha256(mac).hexdigest()
+    return mac
+
+def checkLicence():
+    pub_key = """-----BEGIN RSA PUBLIC KEY-----
+    MIGJAoGBAMhqydgWZUV7qy96aGTY6i/pGC5mC8AyjvwIwoH2zE6hi5MQsW5cpOLS
+    d2VNhfi2ypg19w3Z2sd248X/fc4lGwDwP8/fXNoRtVBDR/3F/+WlaK9beFIyp5J4
+    Fa2XHj5lOiCjLoJpzehE2Dguv+3xORJn10oGAHQhXxFjdWEt5xBBAgMBAAE=
+    -----END RSA PUBLIC KEY-----"""
+
+    mac = getMac()
+
+    with open(os.path.dirname(__file__) + "/../licence", 'rb') as x:
+        licence = x.read()
+
+    d_rsa = Crypto.PublicKey.RSA.importKey(pub_key)
+    verifer = Crypto.Signature.PKCS1_v1_5.new(d_rsa)
+    msg_hash = Crypto.Hash.SHA256.new()
+    msg_hash.update(mac.encode('utf-8'))
+    return verifer.verify(msg_hash, base64.decodebytes(licence))
+
+
+
+

+ 7 - 0
utils/permission.py

@@ -1,15 +1,22 @@
 # coding=utf-8
 
 from rest_framework import permissions
+from django.utils import timezone
+
+from apps.staff.models import User
 
 class IsStaff(permissions.BasePermission):
     def has_permission(self, request, view):
         if not request.user or not request.user.is_authenticated:
             return False
+
+        User.objects.filter(pk=request.user.pk).select_for_update().update(last_refresh=timezone.now())
         return request.user.is_staff()
 
 class IsAdministrator(permissions.BasePermission):
     def has_permission(self, request, view):
         if not request.user or not request.user.is_authenticated:
             return False
+
+        User.objects.filter(pk=request.user.pk).select_for_update().update(last_refresh=timezone.now())
         return request.user.is_administrator()

+ 0 - 23
vue/.gitignore

@@ -1,23 +0,0 @@
-.DS_Store
-node_modules
-/dist
-
-
-# local env files
-.env.local
-.env.*.local
-
-# Log files
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-
-# Editor directories and files
-.idea
-.vscode
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?

+ 5 - 0
vue/README.md

@@ -1,5 +1,10 @@
 # demo
 
+## 项目简介
+```
+这是一个驾照考试的前端项目,具有登录、知识库、习题练习、模拟考试、正式考试、考试记录、错题集功能页面
+```
+
 ## Project setup
 ```
 npm install

二進制
vue/dist.zip


+ 1 - 1
vue/package.json

@@ -1,5 +1,5 @@
 {
-  "name": "vue",
+  "name": "",
   "version": "0.1.0",
   "private": true,
   "scripts": {

+ 3 - 6
vue/src/App.vue

@@ -11,12 +11,9 @@ body {
 }
 @font-face {
   font-family: "iconfont"; /* Project id 2724689 */
-  src: url("//at.alicdn.com/t/font_2724689_mtterbh58wm.woff2?t=1631789608637")
-      format("woff2"),
-    url("//at.alicdn.com/t/font_2724689_mtterbh58wm.woff?t=1631789608637")
-      format("woff"),
-    url("//at.alicdn.com/t/font_2724689_mtterbh58wm.ttf?t=1631789608637")
-      format("truetype");
+  src: url("./utils/font_2724689_mtterbh58wm.woff2") format("woff2"),
+    url("./utils/font_2724689_mtterbh58wm.woff") format("woff"),
+    url("./utils/font_2724689_mtterbh58wm.ttf") format("truetype");
 }
 
 .iconfont {

+ 4 - 0
vue/src/api/global.js

@@ -0,0 +1,4 @@
+import { post } from '@/api/request'
+export default {
+  update: query => post("/staff/change_password/", query)
+}

二進制
vue/src/assets/home/1.png


二進制
vue/src/assets/login/1.png


二進制
vue/src/assets/login/2.jpg


二進制
vue/src/assets/login/微信图片_20211231091244.png


二進制
vue/src/assets/logo.png


+ 124 - 21
vue/src/components/Basic/index.vue

@@ -5,10 +5,17 @@
   >
     <el-container>
       <!-- 导航栏 -->
-      <el-header>
+      <el-header
+        :style="{backgroundImage:'url('+nav.icon+')'}"
+        style="background-size:calc(40px * 944 / 84 ) 40px ; background-repeat:no-repeat;background-position:200px"
+      >
         <div class="left">
-          <el-image :src="nav.icon" />
           <span>人员能力评估系统</span>
+          <!-- 944 84  -->
+          <!-- <el-image
+            style="height:50px;width:calc(50px * 944 / 84);padding-left:10px;"
+            :src="nav.icon"
+          /> -->
         </div>
         <div class="right">
           <div
@@ -24,11 +31,64 @@
         <slot />
       </el-main>
     </el-container>
+    <!-- 修改密码 -->
+    <el-dialog
+      title="提示"
+      :visible.sync="centerDialogVisible"
+      width="500px"
+      center
+    >
+      <el-form
+        :rules="rules"
+        :model="value"
+        ref="update"
+        label-width="100px"
+      >
+        <el-form-item
+          label="旧密码"
+          prop="old_password"
+        >
+          <el-input v-model="value.old_password" />
+        </el-form-item>
+        <el-form-item
+          label="新密码"
+          prop="new_password"
+        >
+          <el-input
+            type="password"
+            v-model="value.new_password"
+          />
+        </el-form-item>
+        <el-form-item
+          label="确认密码"
+          prop="confirm_password"
+        >
+          <el-input
+            type="password"
+            v-model="value.confirm_password"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            type="primary"
+            @click="submitForm('update')"
+          >提交</el-button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+    <!-- logo -->
+    <!-- <img
+      src="@/assets/login/2.jpg"
+      class="logo"
+      style="width:200px;position:fixed;bottom:50px;right:50px;"
+      alt=""
+    > -->
   </div>
 </template>
 
 <script>
-import icon from "@/assets/home/u25.png"
+import icon from "@/assets/login/1.png";
+import Global from "@/api/global.js"
 export default {
   props: {
     Type: {
@@ -37,41 +97,75 @@ export default {
     }
   },
   data () {
+    var validatorAFunc = (rule, value, callback) => {
+      this.value.old_password == value ? callback(new Error("新密码不能和记密码一致!")) :
+        callback();
+    }
+    var validatorBFunc = (rule, value, callback) => {
+      this.value.new_password !== value ? callback(new Error("两次密码不一致")) :
+        callback();
+    }
     return {
       // 导航栏
       nav: {
         icon,
         btns: [{
-          name: "账户设置", success (that) {
-            alert("账户设置")
+          name: "用户 - " + sessionStorage.getItem("username"), success () {
+            this.centerDialogVisible = true;
           }
         },
         {
           name: "退出登录",
-          success (that) {
+          success () {
             sessionStorage.clear();
-            that.$router.push("/login")
+            this.$router.push("/login")
           }
         }
         ]
       },
+      // 修改密码
+      centerDialogVisible: false,
+      value: {
+        old_password: '',
+        new_password: '',
+        confirm_password: "",
+      },
+      rules: {
+        old_password: [
+          { required: true, message: "旧密码不能为空!", trigger: "blur" },
+          { min: 4, message: "旧密码最短4个字符", trigger: "blur" },
+        ],
+        new_password: [
+          { required: true, message: "新密码不能为空!", trigger: "blur" },
+          { min: 4, message: "新密码最短4个字符!", trigger: "blur" },
+          { validator: validatorAFunc, trigger: "blur" }
+        ],
+        confirm_password: [
+          { required: true, message: "确认密码不能为空!", trigger: "blur" },
+          { validator: validatorBFunc, trigger: "blur" }
+        ]
+      }
     }
   },
   methods: {
-    Success (func) {
-      let that = this;
-      func(that)
-    },
-    // 正式考试 严格模式
-    Static () {
-      // window.onbeforeunload = function (e) {
-      //   var e = window.event || e;
-      //   e.returnValue = ("确定离开当前页面吗?");
-      // }
-      // console.log(this.Type, this.$refs)
-      // this.$refs["basic"].addEventListener("mouseleave", function () {
-      //   alert("123")
-      // })
+    Success (func) { func.bind(this)() },
+    // 提交数据
+    submitForm (refName) {
+      this.$refs[refName].validate(valid => {
+        if (valid) {
+          Global.update(this.value).then(res => {
+            this.$message({ type: "success", message: "密码已修改!" });
+            sessionStorage.clear();
+            this.value = {
+              old_password: '',
+              new_password: '',
+              confirm_password: "",
+            }
+            this.centerDialogVisible = false;
+            this.$router.push("/login")
+          })
+        }
+      })
     }
   },
   mounted () {
@@ -80,8 +174,17 @@ export default {
 }
 </script>
 <style lang="less" >
+.logo {
+  display: none;
+}
+@media screen and (min-width: 1680px) {
+  .logo {
+    display: block;
+  }
+}
 #basic {
   min-height: 100vh;
+  position: relative;
   .el-container {
     // h:60 p:0 20
     .el-header {

+ 0 - 12
vue/src/components/TestPaper/components/bool.vue

@@ -17,14 +17,6 @@
         </el-radio-group>
       </div>
     </div>
-    <div
-      v-if="State"
-      class="right"
-    >
-      <div class="header">共错误3次,最后一次出错:2021-04-26 15:23,XXXXXX模拟考试</div>
-      <div class="body">解析:
-        XXXXXX</div>
-    </div>
   </div>
 </template>
 <script>
@@ -93,9 +85,5 @@ export default {
       margin: 5px 0;
     }
   }
-  .right {
-    width: 400px;
-    background-color: #f4f7ed;
-  }
 }
 </style>

+ 1 - 12
vue/src/components/TestPaper/components/checkbox.vue

@@ -15,14 +15,6 @@
         />
       </div>
     </div>
-    <div
-      v-if="State"
-      class="right"
-    >
-      <div class="header">共错误3次,最后一次出错:2021-04-26 15:23,XXXXXX模拟考试</div>
-      <div class="body">解析:
-        XXXXXX</div>
-    </div>
   </div>
 </template>
 <script>
@@ -61,6 +53,7 @@ export default {
   display: flex;
   .left {
     width: 100%;
+    overflow: hidden;
     h3 {
       margin-top: 0;
     }
@@ -68,9 +61,5 @@ export default {
       margin: 5px 0;
     }
   }
-  .right {
-    width: 400px;
-    background-color: #f4f7ed;
-  }
 }
 </style>

+ 0 - 12
vue/src/components/TestPaper/components/input.vue

@@ -22,14 +22,6 @@
         </div>
       </div>
     </div>
-    <div
-      v-if="State"
-      class="right"
-    >
-      <div class="header">共错误3次,最后一次出错:2021-04-26 15:23,XXXXXX模拟考试</div>
-      <div class="body">解析:
-        XXXXXX</div>
-    </div>
   </div>
 </template>
 <script>
@@ -122,9 +114,5 @@ export default {
       }
     }
   }
-  .right {
-    width: 400px;
-    background-color: #f4f7ed;
-  }
 }
 </style>

+ 0 - 12
vue/src/components/TestPaper/components/radio.vue

@@ -16,14 +16,6 @@
         />
       </div>
     </div>
-    <div
-      v-if="State"
-      class="right"
-    >
-      <div class="header">共错误3次,最后一次出错:2021-04-26 15:23,XXXXXX模拟考试</div>
-      <div class="body">解析:
-        XXXXXX</div>
-    </div>
   </div>
 </template>
 <script>
@@ -72,9 +64,5 @@ export default {
       margin: 15px 0;
     }
   }
-  .right {
-    width: 400px;
-    background-color: #f4f7ed;
-  }
 }
 </style>

+ 9 - 8
vue/src/components/TestPaper/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div id="testpaper">
+  <div class="testpaper">
     <div class="card">
       <el-card shadow="hover">
         <Radio
@@ -40,10 +40,7 @@
         </p>
       </el-card>
     </div>
-    <div
-      v-if="!State"
-      class="list"
-    >
+    <div class="list">
       <el-card
         style="background-color:#f03;color:#fff;text-align:center"
         shadow="never"
@@ -205,6 +202,9 @@ export default {
   },
   watch: {
     Data (val, oldval) {
+      clearInterval(this.timer)
+      this.Type == 'formal' ?
+        this.TimeFunc(false) : this.TimeFunc();
       val.id == 1 ? this.A = true : this.A = false;
       val.id == this.Total ? this.B = true : this.B = false;
       this.value = [];
@@ -216,14 +216,15 @@ export default {
 }
 </script>
 <style lang="less" scoped>
-#testpaper {
+.testpaper {
   display: flex;
   .card {
-    width: 100%;
+    width: 910px;
     box-sizing: border-box;
+    overflow: hidden;
   }
   .list {
-    width: 300px;
+    width: 230px !important;
     margin-left: 20px;
   }
 

+ 4 - 2
vue/src/components/WTestPaper/components/bool.vue

@@ -107,8 +107,10 @@ export default {
 }
 .bool {
   display: flex;
+  justify-content: space-around;
   .left {
-    width: 100%;
+    width: 800px;
+    overflow: hidden;
     h3 {
       margin-top: 0;
     }
@@ -117,7 +119,7 @@ export default {
     }
   }
   .right {
-    width: 400px;
+    width: 300px;
     background-color: #f4f7ed;
     .header {
       padding: 10px;

+ 4 - 2
vue/src/components/WTestPaper/components/checkbox.vue

@@ -75,8 +75,10 @@ export default {
 <style lang="less" scoped>
 .checkbox {
   display: flex;
+  justify-content: space-around;
   .left {
-    width: 100%;
+    width: 800px;
+    overflow: hidden;
     h3 {
       margin-top: 0;
     }
@@ -85,7 +87,7 @@ export default {
     }
   }
   .right {
-    width: 400px;
+    width: 300px;
     background-color: #f4f7ed;
     .header {
       padding: 10px;

+ 4 - 2
vue/src/components/WTestPaper/components/input.vue

@@ -109,8 +109,10 @@ export default {
 <style lang="less" scoped>
 .input {
   display: flex;
+  justify-content: space-around;
   .left {
-    width: 100%;
+    width: 800px;
+    overflow: hidden;
     h3 {
       margin-top: 0;
     }
@@ -138,7 +140,7 @@ export default {
     }
   }
   .right {
-    width: 400px;
+    width: 300px;
     background-color: #f4f7ed;
     .header {
       padding: 10px;

+ 4 - 2
vue/src/components/WTestPaper/components/radio.vue

@@ -75,8 +75,10 @@ export default {
 <style lang="less" scoped>
 .radio {
   display: flex;
+  justify-content: space-around;
   .left {
-    width: 100%;
+    width: 800px;
+    overflow: hidden;
     h3 {
       margin-top: 0;
     }
@@ -85,7 +87,7 @@ export default {
     }
   }
   .right {
-    width: 400px;
+    width: 300px;
     background-color: #f4f7ed;
     .header {
       padding: 10px;

二進制
vue/src/utils/font_2724689_mtterbh58wm.ttf


二進制
vue/src/utils/font_2724689_mtterbh58wm.woff


二進制
vue/src/utils/font_2724689_mtterbh58wm.woff2


+ 2 - 1
vue/src/views/Exam/formal_exam.vue

@@ -73,7 +73,7 @@ export default {
       },
       dialogTableVisible: false,//反馈
       practise: null,//请求题目套卷的唯一表示
-      name: "第一次世界大战知识综合测试",//考卷的名字
+      name: "",//考卷的名字
       state: false, //false stateData必须 做题状态
       timer: 0,//计时器
       Unselected: 0,//未做的题
@@ -178,6 +178,7 @@ export default {
           total_question = [],//总题目id储存
           Unselected = 0//几道题未做
           ;
+        this.name = data.question_data.exam_title
         // 解决排序跳转问题
         let show_question_id = data.question_data.id, next_question_id = null, top_question_id = null, show_question_li = null, i = null;
         function mapfunc (data) {

+ 2 - 1
vue/src/views/Exam/index.vue

@@ -71,7 +71,7 @@ export default {
       },
       dialogTableVisible: false,//反馈
       practise: null,//请求题目套卷的唯一表示
-      name: "第一次世界大战知识综合测试",//考卷的名字
+      name: "",//考卷的名字
       state: false, //false stateData必须 做题状态
       timer: 0,//计时器
       Unselected: 0,//未做的题
@@ -175,6 +175,7 @@ export default {
           total_question = [],//总题目id储存
           Unselected = 0//几道题未做
           ;
+        this.name = data.question_data.exam_title;
         // 解决排序跳转问题
         let show_question_id = data.question_data.id, next_question_id = null, top_question_id = null, show_question_li = null, i = null;
         function mapfunc (data) {

+ 3 - 1
vue/src/views/Exam/mock_exam.vue

@@ -71,7 +71,7 @@ export default {
       },
       dialogTableVisible: false,//反馈
       practise: null,//请求题目套卷的唯一表示
-      name: "第一次世界大战知识综合测试",//考卷的名字
+      name: "",//考卷的名字
       state: false, //false stateData必须 做题状态
       timer: 0,//计时器
       Unselected: 0,//未做的题
@@ -175,6 +175,8 @@ export default {
           total_question = [],//总题目id储存
           Unselected = 0//几道题未做
           ;
+        this.name = data.question_data.exam_title
+        // this.timer = (new Date - new Date(data.question_data.create_time)) / 1000;
         // 解决排序跳转问题
         let show_question_id = data.question_data.id, next_question_id = null, top_question_id = null, show_question_li = null, i = null;
         function mapfunc (data) {

+ 2 - 2
vue/src/views/Home/pages/components/P_tab_a.vue

@@ -1,10 +1,10 @@
 <template>
   <div class="taba">
     <!-- 提示 -->
-    <p class="msg">
+    <!-- <p class="msg">
       <span>{{msg}}</span>
       <span class="link">继续练习</span>
-    </p>
+    </p> -->
     <!-- table -->
     <Table
       v-for='(item,index) in tableData'

+ 0 - 1
vue/src/views/Home/pages/formal_examination.vue

@@ -131,7 +131,6 @@ export default {
       })
     },
     handleClick (data) {
-      console.log(data)
       let time = (new Date(data.exam_time) - new Date()) / 1000;
       this.dialog = {
         id: data.id,

+ 8 - 4
vue/src/views/Home/pages/knowledge_base.vue

@@ -166,7 +166,7 @@ export default {
   }
 }
 </script>
-<style lang="less" scoped>
+<style lang="less">
 .knowledgebase {
   display: flex;
   .left,
@@ -202,14 +202,18 @@ export default {
     }
   }
   .left {
-    width: 300px;
+    width: 150px;
   }
   .center {
-    width: 100%;
+    width: 820px;
     margin: 0 20px;
+    overflow: hidden;
+    img {
+      max-width: 790px;
+    }
   }
   .right {
-    width: 300px;
+    width: 150px;
     .content {
       p {
         margin: 0;

+ 7 - 0
vue/src/views/Login/index.vue

@@ -2,6 +2,7 @@
   <div
     ref="login"
     class="login"
+    style="position: relative;"
   >
     <div class="card">
       <el-card shadow="hover">
@@ -46,6 +47,12 @@
         </div>
       </el-card>
     </div>
+    <img
+      style="position:absolute;bottom:0;height:50px"
+      src="@/assets/login/1.png"
+      alt=""
+      class="logo"
+    >
   </div>
 </template>
 <script>

+ 1 - 0
生成授权码/key

@@ -0,0 +1 @@
+a248cc2184e6ea930541839ed68d747028c83ddb3bf33aa993f3a5733137b6ca

+ 1 - 0
生成授权码/licence

@@ -0,0 +1 @@
+BeMvcLZPezbxdmeTh7Wyop3h6+MfJZjhCMpxJTydzYkC7vhcZMWptkIpWX38ynX9L7QqH9tYhcd0MROWLP06yzX0fxXHDPRp+w5baR3wzMAh2PTIzvTPKGGok8Iu4rOUsMPoZwjfhEThzbOkezBHYzcahL/HkvMA3wrqvbnPEig=

+ 36 - 0
生成授权码/sign.py

@@ -0,0 +1,36 @@
+#coding=utf-8
+
+import os
+import Crypto.Hash
+import Crypto.PublicKey.RSA
+import Crypto.Signature.PKCS1_v1_5
+import base64
+
+priv_key = """-----BEGIN RSA PRIVATE KEY-----
+MIICYQIBAAKBgQDIasnYFmVFe6svemhk2Oov6RguZgvAMo78CMKB9sxOoYuTELFu
+XKTi0ndlTYX4tsqYNfcN2drHduPF/33OJRsA8D/P31zaEbVQQ0f9xf/lpWivW3hS
+MqeSeBWtlx4+ZTogoy6Cac3oRNg4Lr/t8TkSZ9dKBgB0IV8RY3VhLecQQQIDAQAB
+AoGAMINpCJ2jNgaRkZSX4JGBXseVyuV4wrV6VxfnvX34RrBkEN1hlc1nPGCl9iel
+3mag8+dcPkYV52KoEC2gFZHc45/X8+MNAB/a3pYTK64VJE6mjEKSfq4nVWrCiIY2
+vHBSNuGI+9H5j6lYVbukuT6X+D7u9BD1+ozcUHcjFIgrVWECRQDqpN9Fq8Ub/PYS
+b+xRchkQEieZxwWmD1/gcUxp5X7P6VRgErh/cPD3QZlG5kQ/OLeGSPpYFXhbVyhR
+yB2rLVMbuAK78wI9ANqocPAjFAcCVn6I/+f1qAhqg/AURULFRHUCOf/mVPhC0Jc7
++yxSfYueNjuE5KapfPrOAJV8GjEO7dNT+wJENnXb6ITMtAlLZ84YcHLmBEfibxu1
+YOySmTpSvQVqIIGMdtwBfHrPQuQz2jPZxT65we4wRL9+9txM3GZxFGjpsDZOVCcC
+PQC09pjZtT5a+q1Y9ctNTzstE/Jz3GLh+t9IM3qK9ja2bJ2zvHmI2hB7X4okwjx2
+TmlYLOvAy7/lgCSGNMcCRQCAEpmLCgDV5cy+x+XZR1p7xvED87kaa0jZ8UQtcET7
+gRdW88GKbBXJaWUeFsLjOwuLWYXloDYyDZR9jtM12bzQf+LJnQ==
+-----END RSA PRIVATE KEY-----"""
+
+mac = ''
+with open("key", 'rb') as x:
+	mac = x.read()
+
+c_rsa = Crypto.PublicKey.RSA.importKey(priv_key)
+signer = Crypto.Signature.PKCS1_v1_5.new(c_rsa)
+msg_hash = Crypto.Hash.SHA256.new()
+msg_hash.update(mac)
+sign = base64.b64encode(signer.sign(msg_hash)).decode('utf-8')
+
+with open('licence', mode='wb') as f:
+	f.write(sign.encode('utf-8'))  

+ 1 - 0
获取机器码/key

@@ -0,0 +1 @@
+a248cc2184e6ea930541839ed68d747028c83ddb3bf33aa993f3a5733137b6ca

+ 40 - 0
获取机器码/mac.py

@@ -0,0 +1,40 @@
+#coding=utf-8
+import uuid
+import datetime
+import os
+
+import platform
+import base64
+import hashlib
+
+XorKey = [0xB2, 0x09, 0xBB, 0x55, 0x93, 0x83, 0x03, 0x24]
+
+def enc(src):
+    j, result = 0, ""
+    for s in src:
+        result = result + hex(ord(s) ^ (XorKey[j]))[2:]
+        j = (j + 1) % 8
+    return result
+
+sysstr = platform.system()
+if (sysstr == "Windows"):
+    import winreg
+    key = winreg.OpenKey(
+        winreg.HKEY_LOCAL_MACHINE,
+        "SOFTWARE\\Microsoft\\Cryptography",
+        0,
+        winreg.KEY_READ | winreg.KEY_WOW64_64KEY
+    )
+    result = winreg.QueryValueEx(key, "MachineGuid")
+    mac = result[0]
+else:
+    mac = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1])
+
+path = '/auth/kaohe/v2/'
+mac += 'ZZLY[' + path + ']20190325'
+mac = enc(mac)
+mac = base64.b64encode(mac.encode())
+mac = hashlib.sha256(mac).hexdigest()
+
+with open('key', mode='wb') as f:
+	f.write(mac.encode('utf-8'))