瀏覽代碼

Merge branch 'master' of http://git.zzliaoyuan.com:4000/liutao/ks

邪性 3 年之前
父節點
當前提交
f62266de43
共有 100 個文件被更改,包括 9594 次插入237 次删除
  1. 0 68
      apps/api/admin/department/serializers.py
  2. 2 1
      apps/api/admin/department/urls.py
  3. 61 25
      apps/api/admin/department/views.py
  4. 14 0
      apps/api/admin/exam/urls.py
  5. 127 0
      apps/api/admin/exam/views.py
  6. 13 0
      apps/api/admin/exampaper/urls.py
  7. 42 0
      apps/api/admin/exampaper/views.py
  8. 13 0
      apps/api/admin/examquestion/urls.py
  9. 59 0
      apps/api/admin/examquestion/views.py
  10. 14 0
      apps/api/admin/knowledge/urls.py
  11. 57 0
      apps/api/admin/knowledge/views.py
  12. 15 0
      apps/api/admin/subject/urls.py
  13. 82 0
      apps/api/admin/subject/views.py
  14. 14 0
      apps/api/admin/syslog/urls.py
  15. 18 0
      apps/api/admin/syslog/views.py
  16. 10 5
      apps/api/admin/urls.py
  17. 14 0
      apps/api/admin/user/urls.py
  18. 33 0
      apps/api/admin/user/views.py
  19. 2 2
      apps/api/admin/views.py
  20. 13 0
      apps/api/staff/errorbook/urls.py
  21. 34 0
      apps/api/staff/errorbook/views.py
  22. 14 0
      apps/api/staff/exam/urls.py
  23. 383 0
      apps/api/staff/exam/views.py
  24. 14 0
      apps/api/staff/knowledge/urls.py
  25. 33 0
      apps/api/staff/knowledge/views.py
  26. 14 0
      apps/api/staff/mock/urls.py
  27. 365 0
      apps/api/staff/mock/views.py
  28. 13 0
      apps/api/staff/practise/urls.py
  29. 293 0
      apps/api/staff/practise/views.py
  30. 9 8
      apps/api/staff/urls.py
  31. 6 6
      apps/api/staff/views.py
  32. 2 2
      apps/dashboard/views.py
  33. 23 0
      apps/examination/exam/filters.py
  34. 148 1
      apps/examination/exam/models.py
  35. 175 0
      apps/examination/exam/serializers.py
  36. 23 0
      apps/examination/exampaper/filters.py
  37. 164 0
      apps/examination/exampaper/models.py
  38. 98 0
      apps/examination/exampaper/serializers.py
  39. 25 0
      apps/examination/examquestion/filters.py
  40. 105 1
      apps/examination/examquestion/models.py
  41. 164 0
      apps/examination/examquestion/serializers.py
  42. 10 0
      apps/examination/urls.py
  43. 21 0
      apps/foundation/filters.py
  44. 12 1
      apps/foundation/models.py
  45. 55 0
      apps/foundation/serializers.py
  46. 24 0
      apps/knowledge/filters.py
  47. 55 0
      apps/knowledge/models.py
  48. 43 0
      apps/knowledge/serializers.py
  49. 13 0
      apps/practise/errorbook/filters.py
  50. 27 2
      apps/practise/errorbook/models.py
  51. 22 0
      apps/practise/errorbook/serializers.py
  52. 66 1
      apps/practise/practiselog/models.py
  53. 89 0
      apps/practise/practiselog/serializers.py
  54. 9 1
      apps/staff/filters.py
  55. 4 2
      apps/staff/models.py
  56. 174 0
      apps/staff/serializers.py
  57. 3 1
      apps/system/filters.py
  58. 16 0
      apps/system/serializers.py
  59. 1 1
      ks/settings.py
  60. 1 1
      ks/urls.py
  61. 21 0
      uis/admin/dashboard/home.html
  62. 95 0
      uis/admin/department/edit.html
  63. 183 0
      uis/admin/department/index.html
  64. 98 0
      uis/admin/exam/details.html
  65. 108 0
      uis/admin/exam/edit.html
  66. 213 0
      uis/admin/exam/examinee_edit.html
  67. 420 0
      uis/admin/exam/exampaper_edit.html
  68. 298 0
      uis/admin/exam/index.html
  69. 203 0
      uis/admin/examlog/details.html
  70. 187 0
      uis/admin/examlog/index.html
  71. 113 0
      uis/admin/exampaper/details.html
  72. 302 0
      uis/admin/exampaper/edit.html
  73. 245 0
      uis/admin/exampaper/index.html
  74. 131 0
      uis/admin/examquestion/details.html
  75. 442 0
      uis/admin/examquestion/edit.html
  76. 218 0
      uis/admin/examquestion/index.html
  77. 81 0
      uis/admin/examquestion_feedback/details.html
  78. 435 0
      uis/admin/examquestion_feedback/edit.html
  79. 227 0
      uis/admin/examquestion_feedback/index.html
  80. 61 14
      uis/admin/index.html
  81. 74 0
      uis/admin/knowledge/details.html
  82. 164 0
      uis/admin/knowledge/edit.html
  83. 258 0
      uis/admin/knowledge/index.html
  84. 83 0
      uis/admin/knowledge_feedback/details.html
  85. 170 0
      uis/admin/knowledge_feedback/edit.html
  86. 228 0
      uis/admin/knowledge_feedback/index.html
  87. 148 0
      uis/admin/log/index.html
  88. 98 94
      uis/admin/login/login.html
  89. 118 0
      uis/admin/permission/index.html
  90. 二進制
      uis/admin/right.png
  91. 113 0
      uis/admin/subject/chapter_edit.html
  92. 259 0
      uis/admin/subject/index.html
  93. 91 0
      uis/admin/subject/subject_edit.html
  94. 166 0
      uis/admin/user/edit.html
  95. 186 0
      uis/admin/user/index.html
  96. 二進制
      uis/admin/wrong.png
  97. 1 0
      uis/dist/css/app.3c9aeb10.css
  98. 1 0
      uis/dist/css/chunk-0669d765.e3f50608.css
  99. 1 0
      uis/dist/css/chunk-09e5b99e.ad48c20d.css
  100. 1 0
      uis/dist/css/chunk-0b9a8692.eb49efda.css

+ 0 - 68
apps/api/admin/department/serializers.py

@@ -1,68 +0,0 @@
-# coding=utf-8
-
-from rest_framework import serializers
-
-from utils.exceptions import CustomError
-from utils.serializersfield import BooleanCharField
-from apps.base import Formater
-from apps.coupon.models import Coupon, CouponItem
-
-
-class CouponSerializer(serializers.ModelSerializer):
-    show_amount = serializers.SerializerMethodField()
-    create_user_text = serializers.CharField(source='create_user.employee.name', read_only=True)
-    date_type_text = serializers.CharField(source="get_date_type_display", read_only=True)
-    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
-
-    def get_show_amount(self, obj):
-        return Formater.formatAmountShow(obj.amount)
-
-    class Meta:
-        model = Coupon
-        fields = '__all__'
-
-    def validate(self, attrs):
-        if 'amount' in attrs:
-            attrs['amount'] = Formater.formatAmount(attrs['amount'])
-        if attrs['validity_begin_time'] and attrs['validity_end_time'] and attrs['validity_begin_time'] > attrs['validity_end_time']:
-            raise CustomError(u'无效的有效期开始/结束时间')
-        if attrs['receive_begin_time'] and attrs['receive_end_time'] and attrs['receive_begin_time'] > attrs['receive_end_time']:
-            raise CustomError(u'无效的领取开始/结束时间')
-        return attrs
-
-    def create(self, validated_data):
-        user = self.context['request'].user
-        validated_data['tenant'] = user.employee.tenant
-        validated_data['create_user'] = user
-        instance = super(CouponSerializer, self).create(validated_data)
-        return instance
-
-    def update(self, instance, validated_data):
-        if instance.tenant != self.context['request'].user.employee.tenant:
-            raise CustomError(u'禁止跨租户操作!')
-        if instance.delete:
-            raise CustomError(u'优惠券[%s]已经被删除,禁止操作' % instance.name)
-        if instance.left_count > validated_data['total_count']:
-            raise CustomError(u'总数量不能小于已领取数量!')
-        instance = super(CouponSerializer, self).update(instance, validated_data)
-        return instance
-
-
-class CouponItemSerializer(serializers.ModelSerializer):
-    write_off_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
-    write_off_user_text = serializers.CharField(source='write_off_user.employee.name', read_only=True)
-    is_used_text = BooleanCharField(source='is_used', read_only=True)
-    validity_begin_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
-    validity_end_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
-    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
-    customer_name = serializers.CharField(source='customer.name', read_only=True)
-    customer_tel = serializers.CharField(source='customer.tel', read_only=True)
-    coupon_name = serializers.CharField(source='coupon.name', read_only=True)
-    amount = serializers.SerializerMethodField()
-
-    def get_amount(self, obj):
-        return Formater.formatAmountShow(obj.amount)
-
-    class Meta:
-        model = CouponItem
-        fields = '__all__'

+ 2 - 1
apps/api/admin/department/urls.py

@@ -6,8 +6,9 @@ from rest_framework.routers import SimpleRouter
 from .views import *
 
 urlpatterns = [
+    url(r'^tree/$', DepartmentTreeView.as_view()),
 ]
 
 router = SimpleRouter()
-router.register(r'', CouponViewSet)
+router.register(r'', DepartmentViewSet)
 urlpatterns += router.urls

+ 61 - 25
apps/api/admin/department/views.py

@@ -1,45 +1,81 @@
 # coding=utf-8
 
-import json
-from rest_framework.decorators import action
-from django.db import transaction
+from django.db import transaction, IntegrityError
+from django.db.models import F, ProtectedError
+from rest_framework.views import APIView
 from utils.permission import IsAdministrator
+from utils import response_error, response_ok
 from utils.custom_modelviewset import CustomModelViewSet
-from apps.coupon.models import Coupon, CouponItem
-from apps.coupon.filters import CouponFilter, CouponItemFilter
-from .serializers import CouponSerializer, CouponItemSerializer
 from utils.exceptions import CustomError
-from utils import response_ok
+from apps.staff.models import Department
+from apps.staff.serializers import DepartmentSerializer
+from apps.staff.filters import DepartmentFilter
 from apps.system.models import SysLog
 
-class CouponViewSet(CustomModelViewSet):
+
+class DepartmentViewSet(CustomModelViewSet):
     permission_classes = [IsAdministrator, ]
-    queryset = Coupon.objects.filter(delete=False)
-    serializer_class = CouponSerializer
+    queryset = Department.objects.filter()
+    serializer_class = DepartmentSerializer
 
     def filter_queryset(self, queryset):
-        queryset = queryset.filter(tenant=self.request.user.employee.tenant)
-        f = CouponFilter(self.request.GET, queryset=queryset)
+        f = DepartmentFilter(self.request.GET, queryset=queryset)
         return f.qs
 
     def perform_create(self, serializer):
-        super(CouponViewSet, self).perform_create(serializer)
+        super(DepartmentViewSet, self).perform_create(serializer)
         instance = serializer.instance
         validated_data = serializer.validated_data
-        tenant_log(self.request.user.employee, BizLog.INSERT, u'添加优惠券[%s],id=%d' % (instance.name, instance.id), validated_data)
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加部门[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def destroy(self, request, *args, **kwargs):
+        try:
+            with transaction.atomic():
+                instance = self.get_object()
+                count = Department.objects.filter(parent_id=instance.id).count()
+                if count:
+                    raise CustomError(u'该部门存在子部门,不允许删除!')
+                SysLog.objects.addnew(self.request.user, SysLog.DELETE, u'删除部门[%s],id=%d' % (instance.name, instance.id))
+                lft = instance.lft
+                rgt = instance.rgt
+
+                total = rgt - lft + 1
+                instance.delete()
+                Department.objects.filter(rgt__gt=lft).update(rgt=F('rgt') - total)
+                Department.objects.filter(lft__gt=lft).update(rgt=F('lft') - total)
+
+        except ProtectedError:
+            return response_error(u'该部门已被引用,禁止删除!')
+        except IntegrityError:
+            return response_error(u'该部门已被引用,禁止删除!')
+        return response_ok()
 
     def perform_update(self, serializer):
-        super(CouponViewSet, self).perform_update(serializer)
+        super(DepartmentViewSet, self).perform_update(serializer)
         instance = serializer.instance
         validated_data = serializer.validated_data
-        tenant_log(self.request.user.employee, BizLog.UPDATE, u'修改优惠券[%s],id=%d' % (instance.name, instance.id), validated_data)
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改部门[%s],id=%d' % (instance.name, instance.id), validated_data)
 
-    def destroy(self, request, *args, **kwargs):
-        with transaction.atomic():
-            instance = self.get_object()
-            if instance.tenant != request.user.employee.tenant:
-                raise CustomError(u'禁止跨租户操作!')
-            instance.delete = True
-            instance.save()
-            tenant_log(self.request.user.employee, BizLog.DELETE, u'删除优惠券[%s],id=%d' % (instance.name, instance.id))
-        return response_ok()
+
+def child_department_tree(parent_id,children):
+    rows = Department.objects.filter(parent_id=parent_id)
+    for row in rows:
+        item = {
+            'id': row.id,
+            'value': row.id,
+            'name': row.name,
+            'notes': row.notes,
+            'parent_id': row.parent_id,
+            'children': []
+        }
+        child_department_tree(row.id, item['children'])
+        children.append(item)
+
+
+class DepartmentTreeView(APIView):
+    permission_classes = [IsAdministrator, ]
+
+    def get(self, request):
+        result = []
+        child_department_tree(None, result)
+        return response_ok(result)

+ 14 - 0
apps/api/admin/exam/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+]
+
+router = SimpleRouter()
+router.register(r'examlog', ExamLogViewSet)
+router.register(r'', ExamViewSet)
+urlpatterns += router.urls

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

@@ -0,0 +1,127 @@
+# coding=utf-8
+
+import json
+from django.utils import timezone
+from django.db import transaction
+from rest_framework.viewsets import ReadOnlyModelViewSet
+from rest_framework.decorators import action
+from utils.permission import IsAdministrator
+from utils import response_error, response_ok
+from utils.custom_modelviewset import CustomModelViewSet
+from utils.exceptions import CustomError
+from apps.system.models import SysLog
+
+from apps.examination.exampaper.models import ExamPaper
+from apps.examination.exampaper.serializers import ExamPaperSerializer
+from apps.examination.exam.models import Exam, ExamLog, ExamAnswerLog
+from apps.examination.exam.filters import ExamFilter, FormalExamLogFilter
+from apps.examination.exam.serializers import ExamSerializer, FormalExamLogSerializer
+
+class ExamViewSet(CustomModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = Exam.objects.filter(delete=False)
+    serializer_class = ExamSerializer
+
+    def filter_queryset(self, queryset):
+        f = ExamFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(ExamViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加考试[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(ExamViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改考试[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def destroy(self, request, *args, **kwargs):
+        with transaction.atomic():
+            instance = self.get_object()
+            instance.delete = True
+            instance.save()
+            SysLog.objects.addnew(self.request.user, SysLog.DELETE, u'删除考试[%s],id=%d' % (instance.name, instance.id))
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def add_examinee(self, request, pk):
+        user_ids = json.loads(request.POST.get('user_ids'))
+
+        with transaction.atomic():
+            exam = Exam.getById(pk)
+            if timezone.now() > exam.exam_time:
+                raise CustomError(u'考试已经开始,禁止修改!')
+            exam.change_examinee(user_ids)
+            exam.save()
+            SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'添加考生,id=%d' % exam.id, request.POST.get('user_ids'))
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def allocation_fixed_exampaper(self, request, pk):
+        exampaper_id = request.POST.get('exampaper_id')
+
+        with transaction.atomic():
+            exam = Exam.getById(pk)
+            if timezone.now() > exam.exam_time:
+                raise CustomError(u'考试已经开始,禁止修改!')
+            exampaper = ExamPaper.getById(exampaper_id)
+            exam.allocation_exampaper(Exam.FIXED, exampaper)
+            exam.save()
+            SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'分配固定试卷,id=%d' % exam.id, request.POST.get('exampaper_id'))
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def allocation_random_exampaper(self, request, pk):
+        data = request.data.copy()
+
+        with transaction.atomic():
+            exam = Exam.getById(pk)
+            if timezone.now() > exam.exam_time:
+                raise CustomError(u'考试已经开始,禁止修改!')
+
+            data['name'] = exam.name
+            data['type'] = ExamPaper.RANDOM
+            data['passline'] = 0
+            exampaper_serializer = ExamPaperSerializer(data=data, context={'request': request})
+            exampaper_serializer.is_valid(raise_exception=True)
+            exampaper = exampaper_serializer.save()
+            exampaper.generate_passline()
+            exampaper.save()
+
+            exam.allocation_exampaper(Exam.RANDOM, exampaper)
+            exam.save()
+
+            data['id'] = exampaper.id
+            SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'分配随机试卷,id=%d' % exam.id, data)
+        return response_ok()
+
+    @action(methods=['get'], detail=True)
+    def examinee(self, request, pk):
+        result = []
+        rows = ExamLog.objects.filter(exam_id=pk, delete=False).values('user_id', 'user__username','user__name', 'user__department__name')
+        for row in rows:
+            item = {
+                'id':row['user_id'],
+                'username': row['user__username'],
+                'name': row['user__name'],
+                'department_text': row['user__department__name'],
+            }
+            result.append(item)
+        return response_ok(result)
+
+class ExamLogViewSet(ReadOnlyModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = ExamLog.objects.filter(type=ExamLog.FORMAL, delete=False)
+    serializer_class = FormalExamLogSerializer
+
+    def filter_queryset(self, queryset):
+        f = FormalExamLogFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    @action(methods=['get'], detail=True)
+    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))

+ 13 - 0
apps/api/admin/exampaper/urls.py

@@ -0,0 +1,13 @@
+# coding=utf-8
+
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+]
+
+router = SimpleRouter()
+router.register(r'', ExamPaperViewSet)
+urlpatterns += router.urls

+ 42 - 0
apps/api/admin/exampaper/views.py

@@ -0,0 +1,42 @@
+# coding=utf-8
+
+from django.db import transaction
+from rest_framework.views import APIView
+from utils.permission import IsAdministrator
+from utils import response_error, response_ok
+from utils.custom_modelviewset import CustomModelViewSet
+from apps.system.models import SysLog
+
+from apps.examination.exampaper.models import ExamPaper
+from apps.examination.exampaper.filters import ExamPaperFilter
+from apps.examination.exampaper.serializers import ExamPaperSerializer
+
+
+class ExamPaperViewSet(CustomModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = ExamPaper.objects.filter(delete=False)
+    serializer_class = ExamPaperSerializer
+
+    def filter_queryset(self, queryset):
+        f = ExamPaperFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(ExamPaperViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加试卷[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(ExamPaperViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改试卷[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def destroy(self, request, *args, **kwargs):
+        with transaction.atomic():
+            instance = self.get_object()
+            instance.delete = True
+            instance.save()
+            SysLog.objects.addnew(self.request.user, SysLog.DELETE, u'删除试卷[%s],id=%d' % (instance.name, instance.id))
+        return response_ok()

+ 13 - 0
apps/api/admin/examquestion/urls.py

@@ -0,0 +1,13 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+]
+
+router = SimpleRouter()
+router.register(r'feedback', ExamQuestionFeedbackViewSet)
+router.register(r'', ExamQuestionViewSet)
+urlpatterns += router.urls

+ 59 - 0
apps/api/admin/examquestion/views.py

@@ -0,0 +1,59 @@
+# coding=utf-8
+import json
+from django.db.models import Sum, F
+from rest_framework.decorators import action
+from rest_framework.views import APIView
+from django.db import transaction
+from rest_framework.viewsets import ReadOnlyModelViewSet
+from django.db.models import Q
+from utils.custom_modelviewset import CustomModelViewSet
+from utils import response_ok, response_error
+from utils.permission import IsAdministrator
+from apps.examination.examquestion.serializers import *
+from apps.examination.examquestion.filters import *
+from apps.system.models import SysLog
+
+class ExamQuestionViewSet(CustomModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = ExamQuestion.objects.filter(delete=False)
+    serializer_class = ExamQuestionSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter()
+        f = ExamQuestionFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(ExamQuestionViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加试题库试题,id=%d' % (instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(ExamQuestionViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改试题库试题,id=%d' % (instance.id), validated_data)
+
+        if 'feedback' in self.request.data and self.request.data['feedback']:
+            feedback = ExamQuestionFeedback.getById(self.request.data['feedback'])
+            feedback.process(self.request.user)
+            feedback.save()
+            SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'处理试题错误反馈,id=%d' % (feedback.id))
+
+    def destroy(self, request, *args, **kwargs):
+        with transaction.atomic():
+            instance = self.get_object()
+            instance.delete = True
+            instance.save()
+            SysLog.objects.addnew(request.user, SysLog.INSERT, u"删除试题库试题,id=%d" % instance.id)
+        return response_ok()
+
+class ExamQuestionFeedbackViewSet(ReadOnlyModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = ExamQuestionFeedback.objects.filter().order_by('status', '-id')
+    serializer_class = ExamQuestionFeedbackSerializer
+
+    def filter_queryset(self, queryset):
+        f = ExamQuestionFeedbackFilter(self.request.GET, queryset=queryset)
+        return f.qs

+ 14 - 0
apps/api/admin/knowledge/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+]
+
+router = SimpleRouter()
+router.register(r'feedback', KnowledgeBaseFeedbackViewSet)
+router.register(r'', KnowledgeBaseViewSet)
+urlpatterns += router.urls

+ 57 - 0
apps/api/admin/knowledge/views.py

@@ -0,0 +1,57 @@
+# coding=utf-8
+
+from django.db import transaction
+from rest_framework.viewsets import ReadOnlyModelViewSet
+from utils.permission import IsAdministrator
+from utils import response_error, response_ok
+from utils.custom_modelviewset import CustomModelViewSet
+from apps.system.models import SysLog
+
+from apps.knowledge.models import KnowledgeBase, KnowledgeBaseFeedback
+from apps.knowledge.filters import KnowledgeBaseFilter, KnowledgeBaseFeedbackFilter
+from apps.knowledge.serializers import KnowledgeBaseSerializer, KnowledgeBaseFeedbackSerializer
+
+
+class KnowledgeBaseViewSet(CustomModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = KnowledgeBase.objects.filter(delete=False)
+    serializer_class = KnowledgeBaseSerializer
+
+    def filter_queryset(self, queryset):
+        f = KnowledgeBaseFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(KnowledgeBaseViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加知识[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(KnowledgeBaseViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改知识[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+        if 'feedback' in self.request.data and self.request.data['feedback']:
+            feedback = KnowledgeBaseFeedback.getById(self.request.data['feedback'])
+            feedback.process(self.request.user)
+            feedback.save()
+            SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'处理知识错误反馈,id=%d' % (feedback.id))
+
+    def destroy(self, request, *args, **kwargs):
+        with transaction.atomic():
+            instance = self.get_object()
+            instance.delete = True
+            instance.save()
+            SysLog.objects.addnew(self.request.user, SysLog.DELETE, u'删除知识[%s],id=%d' % (instance.name, instance.id))
+        return response_ok()
+
+class KnowledgeBaseFeedbackViewSet(ReadOnlyModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = KnowledgeBaseFeedback.objects.filter().order_by('status', '-id')
+    serializer_class = KnowledgeBaseFeedbackSerializer
+
+    def filter_queryset(self, queryset):
+        f = KnowledgeBaseFeedbackFilter(self.request.GET, queryset=queryset)
+        return f.qs

+ 15 - 0
apps/api/admin/subject/urls.py

@@ -0,0 +1,15 @@
+# coding=utf-8
+
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'^dict/$', DictView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'chapter', ChapterViewSet)
+router.register(r'', SubjectViewSet)
+urlpatterns += router.urls

+ 82 - 0
apps/api/admin/subject/views.py

@@ -0,0 +1,82 @@
+# coding=utf-8
+
+from django.db import transaction
+from rest_framework.views import APIView
+from utils.permission import IsAdministrator
+from utils import response_error, response_ok
+from utils.custom_modelviewset import CustomModelViewSet
+from apps.system.models import SysLog
+from apps.foundation.models import Subject, Chapter
+from apps.foundation.filters import SubjectFilter, ChapterFilter
+from apps.foundation.serializers import SubjectSerializer, ChapterSerializer, SubjectSimpleSerializer, ChapterSimpleSerializer
+
+
+class SubjectViewSet(CustomModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = Subject.objects.filter(delete=False)
+    serializer_class = SubjectSerializer
+
+    def filter_queryset(self, queryset):
+        f = SubjectFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(SubjectViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加科目[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(SubjectViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改科目[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def destroy(self, request, *args, **kwargs):
+        with transaction.atomic():
+            instance = self.get_object()
+            instance.delete = True
+            instance.save()
+            SysLog.objects.addnew(self.request.user, SysLog.DELETE, u'删除科目[%s],id=%d' % (instance.name, instance.id))
+        return response_ok()
+
+
+class ChapterViewSet(CustomModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = Chapter.objects.filter(delete=False, subject__delete=False)
+    serializer_class = ChapterSerializer
+
+    def filter_queryset(self, queryset):
+        f = ChapterFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(ChapterViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加章节[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(ChapterViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改章节[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def destroy(self, request, *args, **kwargs):
+        with transaction.atomic():
+            instance = self.get_object()
+            instance.delete = True
+            instance.save()
+            SysLog.objects.addnew(self.request.user, SysLog.DELETE, u'删除章节[%s],id=%d' % (instance.name, instance.id))
+        return response_ok()
+
+
+class DictView(APIView):
+    permission_classes = [IsAdministrator, ]
+
+    def get(self, request):
+        ret = {
+            'subject': SubjectSimpleSerializer(Subject.objects.filter(delete=False), many=True).data,
+            'chapter': ChapterSimpleSerializer(Chapter.objects.filter(delete=False, subject__delete=False), many=True).data
+        }
+        return response_ok(ret)

+ 14 - 0
apps/api/admin/syslog/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'', LogViewSet.as_view()),
+]
+
+# router = SimpleRouter()
+# router.register(r'', LogViewSet)
+# urlpatterns += router.urls

+ 18 - 0
apps/api/admin/syslog/views.py

@@ -0,0 +1,18 @@
+# coding=utf-8
+
+from utils.permission import IsAdministrator
+from rest_framework import generics
+from apps.system.models import SysLog
+from apps.system.filters import SysLogFilter
+from apps.system.serializers import SysLogSerializer
+
+
+class LogViewSet(generics.ListAPIView):
+    permission_classes = [IsAdministrator, ]
+    queryset = SysLog.objects.filter()
+    serializer_class = SysLogSerializer
+
+    def filter_queryset(self, queryset):
+        f = SysLogFilter(self.request.GET, queryset=queryset)
+        return f.qs
+

+ 10 - 5
apps/api/admin/urls.py

@@ -10,8 +10,13 @@ urlpatterns = [
     url(r'^token_refresh/$', AdminUserRefreshTokenView.as_view()),
     url(r'^token_verify/$', AdminUserVerifyTokenView.as_view()),
 
-    url(r'^user/', include('apps.admin.user.urls')),
-    url(r'^tenant/', include('apps.admin.tenant.urls')),
-    url(r'^wechatapp/', include('apps.admin.wechatapplet.urls')),
-    url(r'^wechattp/', include('apps.admin.wechattp.urls')),
-]
+    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')),
+    url(r'^syslog/', include('apps.api.admin.syslog.urls')),
+
+    url(r'^examquestion/', include('apps.api.admin.examquestion.urls')),
+    url(r'^exampaper/', include('apps.api.admin.exampaper.urls')),
+    url(r'^exam/', include('apps.api.admin.exam.urls')),
+    url(r'^knowledge/', include('apps.api.admin.knowledge.urls')),
+]

+ 14 - 0
apps/api/admin/user/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+
+]
+
+router = SimpleRouter()
+router.register(r'', UserViewSet)
+urlpatterns += router.urls

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

@@ -0,0 +1,33 @@
+# coding=utf-8
+
+from django.db import transaction
+from django.contrib.auth import get_user_model
+from utils.permission import IsAdministrator
+from utils import response_error, response_ok
+from utils.custom_modelviewset import CustomModelViewSet
+from utils.exceptions import CustomError
+from apps.staff.serializers import UserSerializer
+from apps.staff.filters import UserFilter
+from apps.system.models import SysLog
+User = get_user_model()
+
+class UserViewSet(CustomModelViewSet):
+    permission_classes = [IsAdministrator, ]
+    queryset = User.objects.filter()
+    serializer_class = UserSerializer
+
+    def filter_queryset(self, queryset):
+        f = UserFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(UserViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加账户[%s],id=%d' % (instance.username, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(UserViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改账户[%s],id=%d' % (instance.username, instance.id), validated_data)

+ 2 - 2
apps/api/admin/views.py

@@ -4,7 +4,7 @@ from django.contrib.auth import get_user_model
 from rest_framework_jwt.views import ObtainJSONWebToken, VerifyJSONWebToken, RefreshJSONWebToken
 from rest_framework.serializers import ValidationError
 from utils import response_error, response_ok
-from .serializers import AdminUserJWTSerializer
+from apps.staff.serializers import AdminUserJWTSerializer
 
 User = get_user_model()
 
@@ -38,4 +38,4 @@ class AdminUserRefreshTokenView(RefreshJSONWebToken):
             if ser.is_valid(raise_exception=True):
                 return response_ok({'token': ser.validated_data['token']})
         except ValidationError as e:
-            return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')
+            return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')

+ 13 - 0
apps/api/staff/errorbook/urls.py

@@ -0,0 +1,13 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'^dict/$', DictView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'', ErrorBookViewSet)
+urlpatterns += router.urls

+ 34 - 0
apps/api/staff/errorbook/views.py

@@ -0,0 +1,34 @@
+# coding=utf-8
+
+from rest_framework.viewsets import ReadOnlyModelViewSet
+from rest_framework.views import APIView
+from utils import response_ok, response_error
+from apps.system.models import SysLog
+from utils.permission import IsAdministrator, IsStaff
+from apps.practise.errorbook.models import ErrorBook
+from apps.practise.errorbook.filters import *
+from apps.practise.errorbook.serializers import *
+from apps.foundation.models import Subject, Chapter
+from apps.foundation.serializers import SubjectSimpleSerializer, ChapterSimpleSerializer
+
+
+class ErrorBookViewSet(ReadOnlyModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = ErrorBook.objects.filter()
+    serializer_class = ErrorBookSerializer
+
+    def filter_queryset(self, queryset):
+        # 排序使用filter排序功能 OrderingFilter  参数 sort=last_time 按最后出错时间排序 sort=-last_time按最后出错时间倒序排序
+        queryset = queryset.filter(create_user=self.request.user)
+        f = ErrorBookFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+
+class DictView(APIView):
+    permission_classes = [IsStaff, ]
+
+    def get(self, request):
+        ret = {
+            'subject': SubjectSimpleSerializer(Subject.objects.filter(delete=False), many=True).data
+        }
+        return response_ok(ret)

+ 14 - 0
apps/api/staff/exam/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+
+]
+
+router = SimpleRouter()
+router.register(r'exam_log', ExamLogViewSet)
+router.register(r'feedback', ExamQuestionFeedbackViewSet)
+urlpatterns += router.urls

+ 383 - 0
apps/api/staff/exam/views.py

@@ -0,0 +1,383 @@
+# coding=utf-8
+import json
+import traceback
+from django.utils import timezone
+from rest_framework.decorators import action
+from django.db import transaction
+from django.db.models import  F
+from utils.custom_modelviewset import CustomModelViewSet
+from utils import response_ok, response_error
+from utils.permission import IsStaff
+from apps.examination.exam.serializers import *
+from apps.examination.exam.filters import *
+from apps.system.models import SysLog
+
+from apps.examination.exam.models import ExamAnswerLog, ExamAnswerOptionLog, ExamAnswerFillLog
+from apps.examination.exampaper.models import ExamPaperDetail, ExamQuestion
+from apps.examination.examquestion.models import ExamQuestionOption, ExamQuestionFill, ExamQuestionFeedback
+from apps.examination.examquestion.serializers import ExamQuestionFeedbackSerializer
+from apps.practise.errorbook.models import ErrorBook
+from utils.format import strftime
+
+class ExamLogViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = ExamLog.objects.filter(delete=False, exam__delete=False, type=ExamLog.FORMAL,)
+    serializer_class = StaffExamLogSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter(user=self.request.user)
+        return queryset
+
+    def retrieve(self, request, *args, **kwargs):
+        instance = self.get_object()
+        serializer = StaffExamLogRetrieveSerializer(instance)
+        answer_log = []
+        answer_logs = ExamAnswerLog.objects.filter(main=instance).order_by('detail__order')
+        for al in answer_logs:
+            item = {
+                'id':al.id,
+                'status':al.status,
+            }
+            answer_log.append(item)
+        ranks = []
+        rank_data = ExamLog.objects.filter(exam=instance.exam, rank__isnull=False, delete=False).order_by('rank').values('user__name','scores','rank')
+        for rank in rank_data:
+            item = {
+                'name':rank['user__name'],
+                'scores':rank['scores'],
+                'rank':rank['rank'],
+            }
+            ranks.append(item)
+        result = {
+            'question': serializer.data,
+            'answer_log': answer_log,
+            'ranks': ranks,
+        }
+        return response_ok(result)
+
+    @action(methods=['get'], detail=False)
+    def get_home_exam(self, request):
+        # 首页,考试提示
+        start_time = timezone.now() + datetime.timedelta(minutes=30)
+        queryset = ExamLog.objects.filter(delete=False,
+                                          type=ExamLog.FORMAL,
+                                          exam__exam_time__lte=start_time,
+                                          user=request.user,
+                                          exam__exam_end_time__gte=timezone.now()
+                                          )
+        data = []
+        for exam_log in queryset:
+            if exam_log.exam.exam_time < timezone.now() < exam_log.exam.exam_end_time:
+                name = '您有一个考试,正在进行中,点击这里立即参加!'
+                id = exam_log.id
+            else:
+                time = (exam_log.exam.exam_time - timezone.now()).seconds
+                name = '您有一个考试,将在{}分钟后开始,请做好准备!'.format(int(time/60) + 1)
+                id = ''
+            item = {
+                'id':id,
+                'name':name,
+                'exam_end_time':exam_log.exam.exam_end_time,
+            }
+            data.append(item)
+        return response_ok(data)
+
+    @action(methods=['get'], detail=False)
+    def get_exam(self, request):
+        # 正式考试,获取考试列表,提前半小时
+        start_time = timezone.now() + datetime.timedelta(minutes=30)
+        queryset = ExamLog.objects.filter(delete=False,
+                                          type=ExamLog.FORMAL,
+                                          exam__exam_time__lte=start_time,
+                                          user=request.user,
+                                          exam__exam_end_time__gte=timezone.now()
+                                          )
+        data = []
+        for exam_log in queryset:
+            item = {
+                'id':exam_log.id,
+                'name':exam_log.exam.name,
+                'subject':exam_log.exam.subject.name,
+                'exam_time':strftime(exam_log.exam.exam_time),
+                'exam_end_time':strftime(exam_log.exam.exam_end_time),
+                'duration':exam_log.exam.duration,
+                'submit_time':exam_log.submit_time and True or False,
+            }
+            data.append(item)
+        return response_ok(data)
+
+    @action(methods=['post'], detail=True)
+    def get_next_practise(self, request, pk):
+        now_practise = request.data.get('now_practise')  # 当前提交的模拟考试明细id。第一题或继续答题时,该参数为空
+        answers = json.loads(request.data.get('answers'))  # 答案, 第一题或继续答题时,该参数为空
+        next_practise = request.data.get('next_practise')  # 下一题id,首次加载第一题,传空
+        submit = request.data.get('submit')  # 交卷1,下一题为空
+
+        try:
+            with transaction.atomic():
+                instance = self.get_object()
+                if instance.submit_time:
+                    raise CustomError('您已交卷,禁止重复答题!')
+                if timezone.now() < instance.exam.exam_time:
+                    raise CustomError('还未到考试时间,请稍等!')
+                if not submit and timezone.now() > instance.exam.exam_end_time:
+                    raise CustomError('考试已结束,禁止答题!')
+                # 点击下一题,保存
+                if now_practise:
+                    detail = ExamPaperDetail.objects.filter(id=now_practise).first()
+                    if not detail:
+                        raise CustomError('提交的考试习题有误,请刷新重试!')
+                    now_question = detail.question
+                    if len(answers) > 0:
+                        answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance,
+                                                                                 detail=detail, )
+                        if now_question.type <= ExamQuestion.MULTIPLE:
+                            # 单选、多选
+                            answers.sort()
+                            ExamAnswerOptionLog.objects.filter(main=answer_log).delete()
+                            for a in answers:
+                                ExamAnswerOptionLog.objects.create(main=answer_log, option_id=a)
+                        elif now_question.type == ExamQuestion.FILL:
+                            # 填空
+                            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)
+                        else:
+                            # 判断
+                            if answers[0] == 1:
+                                answer_log.status = ExamAnswerLog.RIGHT
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                            answer_log.save()
+                    else:
+                        try:
+                            answer_log = ExamAnswerLog.objects.get(main=instance, detail=detail)
+                            ExamAnswerOptionLog.objects.filter(main=answer_log).delete()
+                            ExamAnswerFillLog.objects.filter(main=answer_log).delete()
+                            answer_log.status = ExamAnswerLog.NOTDONE
+                            answer_log.save()
+                        except ExamAnswerLog.DoesNotExist:
+                            # traceback.print_exc()
+                            pass
+
+                question_data = {}
+                # 返回下一题
+                if next_practise:
+                    detail = ExamPaperDetail.objects.filter(id=next_practise, main=instance.exampaper,
+                                                            delete=False).first()
+                else:
+                    detail = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False).first()
+
+                if detail:
+                    question = detail.question
+                    question_data = {
+                        'id': detail.id,
+                        'question': question.id,
+                        'title': question.title,
+                        'next_type': question.type,  # 下一题习题类别
+                        'next_number': detail.order + 1,  # 下下一题序号,
+                        'option': [],
+                    }
+
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=detail).first()
+                    if question.type == ExamQuestion.JUDGMENT:
+                        item1 = {
+                            'id': 1,
+                            'content': '正确',
+                            'answer': True if answer_log and answer_log.status == ExamAnswerLog.RIGHT else False
+                        }
+                        item0 = {
+                            'id': 0,
+                            'content': '错误',
+                            'answer': True if answer_log and answer_log.status == ExamAnswerLog.WRONG else False
+                        }
+                        question_data['option'].append(item1)
+                        question_data['option'].append(item0)
+                    elif question.type <= ExamQuestion.MULTIPLE:
+                        rows = ExamQuestionOption.objects.filter(main=question, delete=False)
+                        for row in rows:
+                            option_log = ExamAnswerOptionLog.objects.filter(main=answer_log, option=row).first()
+                            item = {
+                                'id': row.id,
+                                'content': row.content,
+                                'answer': option_log and True or False
+                            }
+                            question_data['option'].append(item)
+                    elif question.type == ExamQuestion.FILL:
+                        rows = ExamQuestionFill.objects.filter(main=question, delete=False)
+                        for row in rows:
+                            option_log = ExamAnswerFillLog.objects.filter(main=answer_log, order=row.order).first()
+                            item = {
+                                'id': row.order,  # 填空题序号
+                                'content': option_log and option_log.content or '',
+                            }
+                            question_data['option'].append(item)
+
+                # 右侧习题类别列表
+                # 单选、多选、填空。选择答案后,可能会把答案清空,得加上NOTDONE过滤
+                questions = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False).values_list('id',
+                                                                                                              flat=True)
+                single_questions_list = []
+                for single in questions.filter(question__type=ExamQuestion.SINGLE):
+                    answers = ExamAnswerOptionLog.objects.filter(main__main=instance, main__detail=single).first()
+                    single_questions_list.append(
+                        {
+                            'question_id': single,
+                            'complete': answers and True or False,
+                        }
+                    )
+                # 多选题
+                multiple_questions_list = []
+                for multiple in questions.filter(question__type=ExamQuestion.MULTIPLE):
+                    answers = ExamAnswerOptionLog.objects.filter(main__main=instance, main__detail=multiple).first()
+                    multiple_questions_list.append(
+                        {
+                            'question_id': multiple,
+                            'complete': answers and True or False,
+                        }
+                    )
+                # 填空题
+                fill_questions_list = []
+                for fill in questions.filter(question__type=ExamQuestion.FILL):
+                    answers = ExamAnswerFillLog.objects.filter(main__main=instance, main__detail=multiple).first()
+                    fill_questions_list.append(
+                        {
+                            'question_id': fill,
+                            'complete': answers and True or False,
+                        }
+                    )
+                # 判断题
+                judgment_questions_list = []
+                for judgment in questions.filter(question__type=ExamQuestion.JUDGMENT):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=judgment).exclude(status=ExamAnswerLog.NOTDONE)
+                    judgment_questions_list.append(
+                        {
+                            'question_id': judgment,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                result = {
+                    'question_data': question_data,  # 下一题练习题
+                    'single_questions_list': single_questions_list,  # 单选
+                    'multiple_questions_list': multiple_questions_list,  # 多选
+                    'fill_questions_list': fill_questions_list,  # 填空
+                    'judgment_questions_list': judgment_questions_list,  # 判断
+                }
+                return response_ok(result)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(str(e))
+
+    @action(methods=['post'], detail=True)
+    def submit_practise(self, request, pk):
+        # 习题交卷,把上个接口的判断答案,放到此处
+        try:
+            instance = self.get_object()
+            if timezone.now() > instance.exam.exam_end_time:
+                raise CustomError('考试已结束,禁止答题!')
+            if instance.submit_time:
+                raise CustomError('您已交卷,禁止重复交卷!')
+            with transaction.atomic():
+                paper_details = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False)
+                for detail in paper_details:
+                    # 创建模拟考试未答题记录,如果没有找到答案记录,则该题保留未答状态
+                    answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance, detail=detail)
+                    # answer_logs = ExamAnswerLog.objects.filter(main=instance)
+                    # for answer_log in answer_logs:
+                    question = detail.question
+                    if question.type == ExamQuestion.SINGLE:
+                        # 单选
+                        answers = ExamAnswerOptionLog.objects.filter(main=answer_log).first()
+                        if answers:
+                            if answers.option.right:
+                                answer_log.status = ExamAnswerLog.RIGHT
+                                instance.single_answer_count += 1
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                                ErrorBook.add_error(question, request.user, answer_log)
+                    elif question.type == ExamQuestion.MULTIPLE:
+                        # 多选
+                        answers = ExamAnswerOptionLog.objects.filter(main=answer_log).order_by('option_id').values_list(
+                            'option_id', flat=True)
+                        if answers:
+                            right = ExamQuestionOption.objects.filter(main=question, right=True,
+                                                                      delete=False).values_list('id', flat=True)
+                            if list(answers) == list(right):
+                                answer_log.status = ExamAnswerLog.RIGHT
+                                instance.multiple_answer_count += 1
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                                ErrorBook.add_error(question, request.user, answer_log)
+                    elif question.type == ExamQuestion.FILL:
+                        # 填空
+                        fill_logs = ExamAnswerFillLog.objects.filter(main=answer_log)
+                        right = True
+                        if fill_logs:
+                            for fill_log in fill_logs:
+                                right_answer = ExamQuestionFill.objects.filter(main=question, content=fill_log.content,
+                                                                               order=fill_log.order, delete=False)
+                                if not right_answer:
+                                    right = False
+                                    break
+                            if right:
+                                answer_log.status = ExamAnswerLog.RIGHT
+                                instance.fill_answer_count += 1
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                                ErrorBook.add_error(question, request.user, answer_log)
+                    else:
+                        # 判断
+                        if answer_log.status != ExamAnswerLog.NOTDONE:
+                            if question.judgment == (answer_log.status == ExamAnswerLog.RIGHT):
+                                answer_log.status = ExamAnswerLog.RIGHT
+                                instance.judgment_answer_count += 1
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                                ErrorBook.add_error(question, request.user, answer_log)
+                    answer_log.save()
+
+                instance.submit_time = timezone.now()
+                use_time = instance.submit_time - instance.exam_time
+                instance.use_time = use_time.seconds
+
+                single_answer_scores = instance.single_answer_count * instance.exampaper.single_scores
+                multiple_answer_scores = instance.multiple_answer_count * instance.exampaper.multiple_scores
+                fill_answer_scores = instance.fill_answer_count * instance.exampaper.fill_scores
+                judgment_answer_scores = instance.judgment_answer_count * instance.exampaper.judgment_scores
+
+                instance.single_answer_scores = single_answer_scores
+                instance.multiple_answer_scores = multiple_answer_scores
+                instance.fill_answer_scores = fill_answer_scores
+                instance.judgment_answer_scores = judgment_answer_scores
+                instance.scores = single_answer_scores + judgment_answer_scores + multiple_answer_scores + fill_answer_scores
+                # 计算排名
+                up_rank = ExamLog.objects.filter(exam=instance.exam, scores__gte=instance.scores).count()
+                instance.rank = up_rank + 1
+                ExamLog.objects.filter(exam=instance.exam, scores__lte=instance.scores).update(rank=F('rank') + 1)
+                instance.save()
+
+                instance.exampaper.did_count += 1
+                instance.exampaper.save()
+
+                SysLog.objects.addnew(request.user, SysLog.INSERT, u"提交模拟考试题,id=%d" % (instance.id))
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(str(e))
+        return response_ok()
+
+
+class ExamQuestionFeedbackViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = ExamQuestionFeedback.objects.filter()
+    serializer_class = ExamQuestionFeedbackSerializer
+
+    def perform_create(self, serializer):
+        super(ExamQuestionFeedbackViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加试题错误反馈,id=%d' % instance.id, validated_data)

+ 14 - 0
apps/api/staff/knowledge/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+
+]
+
+router = SimpleRouter()
+router.register(r'feedback', KnowledgeBaseFeedbackViewSet)
+router.register(r'', KnowledgeViewSet)
+urlpatterns += router.urls

+ 33 - 0
apps/api/staff/knowledge/views.py

@@ -0,0 +1,33 @@
+# coding=utf-8
+
+from rest_framework.viewsets import ReadOnlyModelViewSet
+from rest_framework.views import APIView
+from utils import response_ok, response_error
+from apps.system.models import SysLog
+from utils.permission import IsAdministrator, IsStaff
+from utils.custom_modelviewset import CustomModelViewSet
+from apps.knowledge.models import KnowledgeBase, KnowledgeBaseFeedback
+from apps.knowledge.filters import KnowledgeBaseFilter
+from apps.knowledge.serializers import KnowledgeBaseSerializer, KnowledgeBaseFeedbackSerializer
+
+
+class KnowledgeViewSet(ReadOnlyModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = KnowledgeBase.objects.filter(delete=False)
+    serializer_class = KnowledgeBaseSerializer
+
+    def filter_queryset(self, queryset):
+        f = KnowledgeBaseFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+
+class KnowledgeBaseFeedbackViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = KnowledgeBaseFeedback.objects.filter()
+    serializer_class = KnowledgeBaseFeedbackSerializer
+
+    def perform_create(self, serializer):
+        super(KnowledgeBaseFeedbackViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加知识错误反馈,id=%d' % instance.id, validated_data)

+ 14 - 0
apps/api/staff/mock/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'^dict/$', DictView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'exam_paper', ExamPaperViewSet)
+router.register(r'exam_log', ExamLogViewSet)
+urlpatterns += router.urls

+ 365 - 0
apps/api/staff/mock/views.py

@@ -0,0 +1,365 @@
+# coding=utf-8
+import json
+import traceback
+from django.utils import timezone
+from rest_framework.decorators import action
+from rest_framework.serializers import ValidationError
+from rest_framework.views import APIView
+from django.db import transaction
+from django.db.models import Q, Sum, F
+from utils.custom_modelviewset import CustomModelViewSet
+from utils import response_ok, response_error
+from utils.permission import IsAdministrator, IsStaff
+from apps.examination.exam.serializers import *
+from apps.examination.exam.filters import *
+from apps.system.models import SysLog
+from apps.foundation.serializers import SubjectSerializer, ChapterSerializer, SubjectSimpleSerializer, \
+    ChapterSimpleSerializer, Subject
+from apps.examination.exam.models import ExamAnswerLog, ExamAnswerOptionLog, ExamAnswerFillLog
+from apps.examination.exampaper.models import ExamPaper, ExamPaperDetail, ExamQuestion
+from apps.examination.exampaper.filters import ExamPaperFilter
+from apps.examination.exampaper.serializers import StaffExamPaperSerializer
+from apps.examination.examquestion.models import ExamQuestionOption, ExamQuestionFill
+from apps.practise.errorbook.models import ErrorBook
+
+
+class ExamPaperViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = ExamPaper.objects.filter(delete=False, type=ExamPaper.MOCK)
+    serializer_class = StaffExamPaperSerializer
+
+    def list(self, request, *args, **kwargs):
+        # 底栏合计
+        # 统计做过多少套
+        queryset = self.filter_queryset(self.get_queryset())
+        queryset = ExamPaperFilter(self.request.GET, queryset=queryset, request=self.request).qs
+        exampaper_ids = ExamLog.objects.filter(user=self.request.user, delete=False).values_list('exampaper_id',
+                                                                                                 flat=True)
+        did_paper = queryset.filter(id__in=exampaper_ids).count()
+        totalRow = {'totalRow': 1, 'did_paper': did_paper, }
+        page = self.paginate_queryset(queryset)
+        if page is not None:
+            serializer = self.get_serializer(page, many=True)
+            data = serializer.data
+            # if len(data) > 0:
+            data.append(totalRow)
+            return self.get_paginated_response(data)
+
+        serializer = self.get_serializer(queryset, many=True)
+        return response_ok(serializer.data)
+
+    # def filter_queryset(self, queryset):
+    #     f = ExamPaperFilter(self.request.GET, queryset=queryset, request=self.request)
+    #     return f.qs
+
+
+class ExamLogViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = ExamLog.objects.filter(delete=False)
+    serializer_class = StaffExamLogSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter(user=self.request.user)
+        return queryset
+
+    def retrieve(self, request, *args, **kwargs):
+        instance = self.get_object()
+        serializer = StaffExamLogRetrieveSerializer(instance)
+        answer_log = []
+        answer_logs = ExamAnswerLog.objects.filter(main=instance).order_by('detail__order')
+        for al in answer_logs:
+            item = {
+                'id': al.id,
+                'status': al.status,
+            }
+            answer_log.append(item)
+        result = {
+            'question': serializer.data,
+            'answer_log': answer_log,
+            'ranks': [],
+        }
+        return response_ok(result)
+
+    def create(self, request, *args, **kwargs):
+        exampaper_id = request.data.get('exampaper')
+
+        try:
+            with transaction.atomic():
+                data = {
+                    'type': ExamPaper.MOCK,
+                    'exampaper': exampaper_id,
+                    'user': request.user.id,
+                    'exam_time': timezone.now()
+                }
+                serializer = StaffExamLogSerializer(data=data)
+                if serializer.is_valid(raise_exception=True):
+                    instance = serializer.save()
+                    result = {
+                        'exam_log': instance.id,  # 模拟考试 id
+                    }
+                    return response_ok(result)
+        except ValidationError as e:
+            traceback.print_exc()
+            return response_error('数据格式有误')
+
+    @action(methods=['post'], detail=True)
+    def get_next_practise(self, request, pk):
+        now_practise = request.data.get('now_practise')  # 当前提交的模拟考试明细id。第一题或继续答题时,该参数为空
+        answers = json.loads(request.data.get('answers'))  # 答案, 第一题或继续答题时,该参数为空
+        next_practise = request.data.get('next_practise')  # 下一题id,首次加载第一题,传空
+
+        try:
+            with transaction.atomic():
+                instance = self.get_object()
+                if instance.submit_time:
+                    raise CustomError('您已交卷,禁止重复答题!')
+                # 点击下一题,保存
+                if now_practise:
+                    detail = ExamPaperDetail.objects.filter(id=now_practise).first()
+                    if not detail:
+                        raise CustomError('提交的考试习题有误,请刷新重试!')
+                    now_question = detail.question
+                    if len(answers) > 0:
+                        answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance,
+                                                                                 detail=detail, )
+                        if now_question.type <= ExamQuestion.MULTIPLE:
+                            # 单选、多选
+                            answers.sort()
+                            ExamAnswerOptionLog.objects.filter(main=answer_log).delete()
+                            for a in answers:
+                                ExamAnswerOptionLog.objects.create(main=answer_log, option_id=a)
+                        elif now_question.type == ExamQuestion.FILL:
+                            # 填空
+                            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)
+                        else:
+                            # 判断
+                            if answers[0] == 1:
+                                answer_log.status = ExamAnswerLog.RIGHT
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                            answer_log.save()
+                    else:
+                        try:
+                            answer_log = ExamAnswerLog.objects.get(main=instance, detail=detail)
+                            ExamAnswerOptionLog.objects.filter(main=answer_log).delete()
+                            ExamAnswerFillLog.objects.filter(main=answer_log).delete()
+                            answer_log.status = ExamAnswerLog.NOTDONE
+                            answer_log.save()
+                        except ExamAnswerLog.DoesNotExist:
+                            # traceback.print_exc()
+                            pass
+
+                question_data = {}
+                # 返回下一题
+                if next_practise:
+                    detail = ExamPaperDetail.objects.filter(id=next_practise, main=instance.exampaper,
+                                                            delete=False).first()
+                else:
+                    detail = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False).first()
+
+                if detail:
+                    question = detail.question
+                    question_data = {
+                        'id': detail.id,
+                        'question': question.id,
+                        'title': question.title,
+                        'next_type': question.type,  # 下一题习题类别
+                        'next_number': detail.order + 1,  # 下下一题序号,
+                        'option': [],
+                    }
+
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=detail).first()
+                    if question.type == ExamQuestion.JUDGMENT:
+                        item1 = {
+                            'id': 1,
+                            'content': '正确',
+                            'answer': True if answer_log and answer_log.status == ExamAnswerLog.RIGHT else False
+                        }
+                        item0 = {
+                            'id': 0,
+                            'content': '错误',
+                            'answer': True if answer_log and answer_log.status == ExamAnswerLog.WRONG else False
+                        }
+                        question_data['option'].append(item1)
+                        question_data['option'].append(item0)
+                    elif question.type <= ExamQuestion.MULTIPLE:
+                        rows = ExamQuestionOption.objects.filter(main=question, delete=False)
+                        for row in rows:
+                            option_log = ExamAnswerOptionLog.objects.filter(main=answer_log, option=row).first()
+                            item = {
+                                'id': row.id,
+                                'content': row.content,
+                                'answer': option_log and True or False
+                            }
+                            question_data['option'].append(item)
+                    elif question.type == ExamQuestion.FILL:
+                        rows = ExamQuestionFill.objects.filter(main=question, delete=False)
+                        for row in rows:
+                            option_log = ExamAnswerFillLog.objects.filter(main=answer_log, order=row.order).first()
+                            item = {
+                                'id': row.order,  # 填空题序号
+                                'content': option_log and option_log.content or '',
+                            }
+                            question_data['option'].append(item)
+
+                # 右侧习题类别列表
+                # 单选、多选、填空。选择答案后,可能会把答案清空,得加上NOTDONE过滤
+                questions = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False).values_list('id',
+                                                                                                              flat=True)
+                single_questions_list = []
+                for single in questions.filter(question__type=ExamQuestion.SINGLE):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=single)
+                    single_questions_list.append(
+                        {
+                            'question_id': single,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                # 多选题
+                multiple_questions_list = []
+                for multiple in questions.filter(question__type=ExamQuestion.MULTIPLE):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=multiple)
+                    multiple_questions_list.append(
+                        {
+                            'question_id': multiple,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                # 填空题
+                fill_questions_list = []
+                for fill in questions.filter(question__type=ExamQuestion.FILL):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=fill)
+                    fill_questions_list.append(
+                        {
+                            'question_id': fill,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                # 判断题
+                judgment_questions_list = []
+                for judgment in questions.filter(question__type=ExamQuestion.JUDGMENT):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=judgment)
+                    judgment_questions_list.append(
+                        {
+                            'question_id': judgment,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                result = {
+                    'question_data': question_data,  # 下一题练习题
+                    'single_questions_list': single_questions_list,  # 单选
+                    'multiple_questions_list': multiple_questions_list,  # 多选
+                    'fill_questions_list': fill_questions_list,  # 填空
+                    'judgment_questions_list': judgment_questions_list,  # 判断
+                }
+                return response_ok(result)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(str(e))
+
+    @action(methods=['post'], detail=True)
+    def submit_practise(self, request, pk):
+        # 习题交卷,把上个接口的判断答案,放到此处
+        try:
+            instance = self.get_object()
+            if instance.submit_time:
+                raise CustomError('您已交卷,禁止重复交卷!')
+            with transaction.atomic():
+                paper_details = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False)
+                for detail in paper_details:
+                    # 创建模拟考试未答题记录,如果没有找到答案记录,则该题保留未答状态
+                    answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance, detail=detail)
+                    # answer_logs = ExamAnswerLog.objects.filter(main=instance)
+                    # for answer_log in answer_logs:
+                    question = detail.question
+                    if question.type == ExamQuestion.SINGLE:
+                        # 单选
+                        answers = ExamAnswerOptionLog.objects.filter(main=answer_log).first()
+                        if answers:
+                            if answers.option.right:
+                                answer_log.status = ExamAnswerLog.RIGHT
+                                instance.single_answer_count += 1
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                                ErrorBook.add_error(question, request.user, answer_log)
+                    elif question.type == ExamQuestion.MULTIPLE:
+                        # 多选
+                        answers = ExamAnswerOptionLog.objects.filter(main=answer_log).order_by('option_id').values_list(
+                            'option_id', flat=True)
+                        if answers:
+                            right = ExamQuestionOption.objects.filter(main=question, right=True,
+                                                                      delete=False).values_list('id', flat=True)
+                            if list(answers) == list(right):
+                                answer_log.status = ExamAnswerLog.RIGHT
+                                instance.multiple_answer_count += 1
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                                ErrorBook.add_error(question, request.user, answer_log)
+                    elif question.type == ExamQuestion.FILL:
+                        # 填空
+                        fill_logs = ExamAnswerFillLog.objects.filter(main=answer_log)
+                        right = True
+                        if fill_logs:
+                            for fill_log in fill_logs:
+                                right_answer = ExamQuestionFill.objects.filter(main=question, content=fill_log.content,
+                                                                               order=fill_log.order, delete=False)
+                                if not right_answer:
+                                    right = False
+                                    break
+                            if right:
+                                answer_log.status = ExamAnswerLog.RIGHT
+                                instance.fill_answer_count += 1
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                                ErrorBook.add_error(question, request.user, answer_log)
+                    else:
+                        # 判断
+                        if answer_log.status != ExamAnswerLog.NOTDONE:
+                            if question.judgment == (answer_log.status == ExamAnswerLog.RIGHT):
+                                answer_log.status = ExamAnswerLog.RIGHT
+                                instance.judgment_answer_count += 1
+                            else:
+                                answer_log.status = ExamAnswerLog.WRONG
+                                ErrorBook.add_error(question, request.user, answer_log)
+                    answer_log.save()
+
+                instance.submit_time = timezone.now()
+                use_time = instance.submit_time - instance.exam_time
+                instance.use_time = use_time.seconds
+
+                single_answer_scores = instance.single_answer_count * instance.exampaper.single_scores
+                multiple_answer_scores = instance.multiple_answer_count * instance.exampaper.multiple_scores
+                fill_answer_scores = instance.fill_answer_count * instance.exampaper.fill_scores
+                judgment_answer_scores = instance.judgment_answer_count * instance.exampaper.judgment_scores
+
+                instance.single_answer_scores = single_answer_scores
+                instance.multiple_answer_scores = multiple_answer_scores
+                instance.fill_answer_scores = fill_answer_scores
+                instance.judgment_answer_scores = judgment_answer_scores
+                instance.scores = single_answer_scores + judgment_answer_scores + multiple_answer_scores + fill_answer_scores
+                instance.save()
+
+                instance.exampaper.did_count += 1
+                instance.exampaper.save()
+
+                SysLog.objects.addnew(request.user, SysLog.INSERT, u"提交模拟考试题,id=%d" % (instance.id))
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(str(e))
+        return response_ok()
+
+
+class DictView(APIView):
+    permission_classes = [IsStaff, ]
+
+    def get(self, request):
+        subjects = Subject.objects.filter(delete=False)
+        serializers = SubjectSerializer(subjects, many=True)
+        return response_ok(serializers.data)

+ 13 - 0
apps/api/staff/practise/urls.py

@@ -0,0 +1,13 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'^dict/$', DictView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'', PractiseLogViewSet)
+urlpatterns += router.urls

+ 293 - 0
apps/api/staff/practise/views.py

@@ -0,0 +1,293 @@
+# coding=utf-8
+import json
+import traceback
+from django.db.models import Sum, F
+from rest_framework.decorators import action
+from rest_framework.serializers import ValidationError
+from rest_framework.views import APIView
+from django.db import transaction
+from django.db.models import Q
+from utils.custom_modelviewset import CustomModelViewSet
+from utils import response_ok, response_error
+from utils.permission import IsAdministrator, IsStaff
+from apps.examination.examquestion.serializers import *
+from apps.examination.examquestion.filters import *
+from apps.system.models import SysLog
+from apps.foundation.serializers import SubjectSerializer, ChapterSerializer, SubjectSimpleSerializer, \
+    ChapterSimpleSerializer, Subject
+from apps.practise.practiselog.models import *
+from apps.practise.practiselog.serializers import PractiseLogSerializer
+
+
+class PractiseLogViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = PractiseLog.objects.filter()
+    serializer_class = PractiseLogSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter(create_user=self.request.user)
+        return queryset
+
+    def create(self, request, *args, **kwargs):
+        subject = request.data.get('subject')
+        chapter = request.data.get('chapter')
+        type = PractiseLog.CHAPTER
+        try:
+            with transaction.atomic():
+                if not chapter:
+                    # 进入练习题,没有选择章节,使用该科目下的第一个章节
+                    # chapter = Chapter.objects.filter(subject=subject, delete=False).order_by('id').first()
+                    type = PractiseLog.SUBJECT
+
+                data = {
+                    'subject': int(subject),
+                    'chapter': chapter and int(chapter) or None,
+                    'type': type,
+                    'create_user': request.user.id,
+                    'submit_time': timezone.now()
+                }
+                serializer = PractiseLogSerializer(data=data)
+                if serializer.is_valid(raise_exception=True):
+                    instance = serializer.save()
+                    result = {
+                        'practise': instance.id,  # 练习题 id
+                    }
+                    return response_ok(result)
+        except ValidationError as e:
+            traceback.print_exc()
+            return response_error('数据格式有误')
+
+    @action(methods=['post'], detail=True)
+    def get_next_practise(self, request, pk):
+        now_practise = request.data.get('now_practise')  # 当前提交的练习题。第一题或继续答题时,该参数为空
+        answers = json.loads(request.data.get('answers'))  # 答案, 第一题或继续答题时,该参数为空
+        next_number = request.data.get('next_number') or 0  # 下一题序号, 第一题提交为空,不在使用
+        next_practise = request.data.get('next_practise')  # 下一题id,首次加载第一题,传空
+        try:
+            with transaction.atomic():
+                instance = self.get_object()
+                # 点击下一题,保存、判断当前习题答案
+                if now_practise:
+                    now_question = ExamQuestion.objects.filter(id=now_practise).first()
+                    if not now_question:
+                        raise CustomError('提交的习题有误,请刷新重试!')
+                    if len(answers) > 0:
+                        answer_log, create = PractiseAnswerLog.objects.get_or_create(main=instance,
+                                                                                     question=now_question, )
+                        if now_question.type == ExamQuestion.SINGLE:
+                            # 单选
+                            PractiseAnswerOptionLog.objects.filter(main=answer_log).delete()
+                            answer = answers[0]
+                            PractiseAnswerOptionLog.objects.create(main=answer_log, option_id=answer)
+                            right = ExamQuestionOption.objects.filter(main=now_question, id=answer, right=True,
+                                                                      delete=False)
+                            if right:
+                                answer_log.status = PractiseAnswerLog.RIGHT
+                            else:
+                                answer_log.status = PractiseAnswerLog.WRONG
+                        elif now_question.type == ExamQuestion.MULTIPLE:
+                            # 多选
+                            answers.sort()
+                            PractiseAnswerOptionLog.objects.filter(main=answer_log).delete()
+                            for a in answers:
+                                PractiseAnswerOptionLog.objects.create(main=answer_log, option_id=a)
+                            right = ExamQuestionOption.objects.filter(main=now_question, right=True,
+                                                                      delete=False).values_list('id', flat=True)
+                            list(right).sort()
+                            if answers == right:
+                                answer_log.status = PractiseAnswerLog.RIGHT
+                            else:
+                                answer_log.status = PractiseAnswerLog.WRONG
+                        elif now_question.type == ExamQuestion.FILL:
+                            # 填空
+                            answers_len = len(answers)
+                            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)
+                                right_answer = ExamQuestionFill.objects.filter(main=now_question, content=answers[a],
+                                                                               order=a + 1)
+                                if not right_answer:
+                                    # 有一个填空错误,整题错误
+                                    right = 0
+                            if right:
+                                answer_log.status = PractiseAnswerLog.RIGHT
+                            else:
+                                answer_log.status = PractiseAnswerLog.WRONG
+                        else:
+                            # 判断
+                            if now_question.judgment == (answers[0] == 1):
+                                answer_log.status = PractiseAnswerLog.RIGHT
+                            else:
+                                answer_log.status = PractiseAnswerLog.WRONG
+                        instance.total_count += 1
+                        # 第一题
+                        if not instance.begin_answer:
+                            instance.begin_answer = answer_log
+                        instance.end_answer = answer_log
+                        instance.submit_time = timezone.now()
+                        instance.save()
+                        answer_log.save()
+                    else:
+                        try:
+                            answer_log = PractiseAnswerLog.objects.get(main=instance, question=now_question, )
+                            PractiseAnswerOptionLog.objects.filter(main=answer_log).delete()
+                            PractiseAnswerFillLog.objects.filter(main=answer_log).delete()
+                            answer_log.status = None
+                            answer_log.save()
+                        except PractiseAnswerLog.DoesNotExist:
+                            # traceback.print_exc()
+                            pass
+
+                question_data = {}
+                if instance.type == PractiseLog.SUBJECT:
+                    questions = ExamQuestion.objects.filter(chapter__subject=instance.subject, delete=False).order_by(
+                        'type')
+                else:
+                    questions = ExamQuestion.objects.filter(chapter=instance.chapter, delete=False).order_by('type')
+                # 返回下一题
+                if next_practise:
+                    question = questions.filter(id=next_practise).first()
+                else:
+                    # 首次加载,选择第一个题
+                    question = questions.filter().first()
+
+                if question:
+                    question_data = {
+                        'id': question.id,
+                        'title': question.title,
+                        'next_type': question.type,  # 下一题习题类别
+                        'next_number': int(next_number) + 1,  # 下下一题序号,
+                        'option': [],
+                    }
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question=question).first()
+                    if question.type == ExamQuestion.JUDGMENT:
+                        item1 = {
+                            'id': 1,
+                            'content': '正确',
+                            'answer': True if answer_log and answer_log.status == PractiseAnswerLog.RIGHT else False
+                        }
+                        item0 = {
+                            'id': 0,
+                            'content': '错误',
+                            'answer': True if answer_log and answer_log.status == PractiseAnswerLog.WRONG else False
+                        }
+                        question_data['option'].append(item1)
+                        question_data['option'].append(item0)
+                    elif question.type <= ExamQuestion.MULTIPLE:
+                        rows = ExamQuestionOption.objects.filter(main=question, delete=False)
+                        for row in rows:
+                            option_log = PractiseAnswerOptionLog.objects.filter(main=answer_log, option=row).first()
+                            item = {
+                                'id': row.id,
+                                'content': row.content,
+                                'answer': option_log and True or False
+                            }
+                            question_data['option'].append(item)
+                    elif question.type == ExamQuestion.FILL:
+                        rows = ExamQuestionFill.objects.filter(main=question, delete=False)
+                        for row in rows:
+                            option_log = PractiseAnswerFillLog.objects.filter(main=answer_log, order=row.order).first()
+                            item = {
+                                'id': row.order,  # 填空题序号
+                                'content': option_log and option_log.content or '',
+                            }
+                            question_data['option'].append(item)
+
+                # 右侧习题类别列表
+                # 单选题
+                # 单选、多选、填空。选择答案后,可能会把答案清空,得加上status__isnull=False过滤
+                questions = questions.values_list('id', flat=True)
+                single_questions_list = []
+                for single in questions.filter(type=ExamQuestion.SINGLE):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=single,
+                                                                  status__isnull=False)
+                    single_questions_list.append(
+                        {
+                            'question_id': single,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                # 多选题
+                multiple_questions_list = []
+                for multiple in questions.filter(type=ExamQuestion.MULTIPLE):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=multiple,
+                                                                  status__isnull=False)
+                    multiple_questions_list.append(
+                        {
+                            'question_id': multiple,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                # 填空题
+                fill_questions_list = []
+                for fill in questions.filter(type=ExamQuestion.FILL):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=fill, status__isnull=False)
+                    fill_questions_list.append(
+                        {
+                            'question_id': fill,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                # 判断题
+                judgment_questions_list = []
+                for judgment in questions.filter(type=ExamQuestion.JUDGMENT):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=judgment)
+                    judgment_questions_list.append(
+                        {
+                            'question_id': judgment,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                result = {
+                    'question_data': question_data,  # 下一题练习题
+                    'single_questions_list': single_questions_list,  # 单选
+                    'multiple_questions_list': multiple_questions_list,  # 多选
+                    'fill_questions_list': fill_questions_list,  # 填空
+                    'judgment_questions_list': judgment_questions_list,  # 判断
+                }
+                return response_ok(result)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(str(e))
+
+    @action(methods=['post'], detail=True)
+    def submit_practise(self, request, pk):
+        # 习题交卷,把上个接口的判断答案,放到此处
+        try:
+            instance = self.get_object()
+
+            with transaction.atomic():
+                right_count = PractiseAnswerLog.objects.filter(main=instance, status=PractiseAnswerLog.RIGHT).count()
+                wrong_count = PractiseAnswerLog.objects.filter(main=instance, status=PractiseAnswerLog.WRONG).count()
+                instance.right_count = right_count
+                instance.wrong_count = wrong_count
+                instance.submit_time = timezone.now()
+                instance.save()
+                SysLog.objects.addnew(request.user, SysLog.INSERT, u"提交习题答案,id=%d" % (instance.id))
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(str(e))
+        return response_ok()
+
+class DictView(APIView):
+    permission_classes = [IsStaff, ]
+
+    def get(self, request):
+        data = []
+        subjects = Subject.objects.filter(delete=False).values('id', 'name').order_by('id')
+        for subject in subjects:
+            chapters = ChapterSimpleSerializer(
+                Chapter.objects.filter(delete=False, subject__delete=False, subject_id=subject['id']).order_by('id'),
+                many=True).data
+            item = {
+                'id': subject['id'],
+                'name': subject['name'],
+                'chapters': chapters,
+            }
+            data.append(item)
+        return response_ok(data)

+ 9 - 8
apps/api/staff/urls.py

@@ -6,12 +6,13 @@ from .views import *
 
 urlpatterns = [
     url(r'^$', index),
-    url(r'^token/obtain/$', AdminUserLoginView.as_view()),
-    url(r'^token_refresh/$', AdminUserRefreshTokenView.as_view()),
-    url(r'^token_verify/$', AdminUserVerifyTokenView.as_view()),
+    url(r'^token/obtain/$', StaffUserLoginView.as_view()),
+    url(r'^token_refresh/$', StaffUserRefreshTokenView.as_view()),
+    url(r'^token_verify/$', StaffUserVerifyTokenView.as_view()),
 
-    url(r'^user/', include('apps.admin.user.urls')),
-    url(r'^tenant/', include('apps.admin.tenant.urls')),
-    url(r'^wechatapp/', include('apps.admin.wechatapplet.urls')),
-    url(r'^wechattp/', include('apps.admin.wechattp.urls')),
-]
+    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')),
+    url(r'^exam/', include('apps.api.staff.exam.urls')),
+    url(r'^knowledge/', include('apps.api.staff.knowledge.urls')),
+]

+ 6 - 6
apps/api/staff/views.py

@@ -4,12 +4,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 utils import response_error, response_ok
-from .serializers import AdminUserJWTSerializer
+from apps.staff.serializers import StaffUserJWTSerializer
 
 User = get_user_model()
 
-class AdminUserLoginView(ObtainJSONWebToken):
-    serializer_class = AdminUserJWTSerializer
+class StaffUserLoginView(ObtainJSONWebToken):
+    serializer_class = StaffUserJWTSerializer
 
     def post(self, request, *args, **kwargs):
         try:
@@ -21,7 +21,7 @@ class AdminUserLoginView(ObtainJSONWebToken):
             return response_error(e.detail['error'][0])
 
 
-class AdminUserVerifyTokenView(VerifyJSONWebToken):
+class StaffUserVerifyTokenView(VerifyJSONWebToken):
     def post(self, request, *args, **kwargs):
         try:
             ser = self.serializer_class(data=request.data)
@@ -31,11 +31,11 @@ class AdminUserVerifyTokenView(VerifyJSONWebToken):
             return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')
 
 
-class AdminUserRefreshTokenView(RefreshJSONWebToken):
+class StaffUserRefreshTokenView(RefreshJSONWebToken):
     def post(self, request, *args, **kwargs):
         try:
             ser = self.serializer_class(data=request.data)
             if ser.is_valid(raise_exception=True):
                 return response_ok({'token': ser.validated_data['token']})
         except ValidationError as e:
-            return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')
+            return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')

+ 2 - 2
apps/dashboard/views.py

@@ -3,7 +3,7 @@
 from django.http import HttpResponseRedirect
 
 def index(request):
-    return HttpResponseRedirect('/staff/login/login.html')
+    return HttpResponseRedirect('/dist/index.html')
 
 def admin_index(request):
-    return HttpResponseRedirect('/admin/login/login.html')
+    return HttpResponseRedirect('/admin/login/login.html')

+ 23 - 0
apps/examination/exam/filters.py

@@ -0,0 +1,23 @@
+# coding=utf-8
+import django_filters
+
+from .models import Exam, ExamLog
+
+class ExamFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
+    subject = django_filters.CharFilter(field_name='subject_id')
+    type = django_filters.CharFilter(field_name='type')
+
+    class Meta:
+        model = Exam
+        fields = "__all__"
+
+class FormalExamLogFilter(django_filters.FilterSet):
+    exam_name = django_filters.CharFilter(field_name="exam__name", lookup_expr="icontains")
+    user_name = django_filters.CharFilter(field_name="user__name", lookup_expr="icontains")
+    exam_subject = django_filters.CharFilter(field_name='exam__subject_id')
+    exam_type = django_filters.CharFilter(field_name='exam__type')
+
+    class Meta:
+        model = ExamLog
+        fields = "__all__"

+ 148 - 1
apps/examination/exam/models.py

@@ -1,17 +1,164 @@
 # coding=utf-8
 
 from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+from utils.exceptions import CustomError
+
+from apps.foundation.models import Subject
+from apps.examination.exampaper.models import ExamPaper, ExamPaperDetail
+from apps.examination.examquestion.models import ExamQuestion, ExamQuestionOption
 
 class Exam(models.Model):
+    FIXED = 1
+    RANDOM = 2
+    TYPE_CHOICES = (
+        (FIXED, u'固定试卷'),
+        (RANDOM, u'随机试卷'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    name = models.CharField(max_length=200, verbose_name=u"名称")
+    subject = models.ForeignKey(Subject, verbose_name=u"科目", on_delete=models.PROTECT, null=True, editable=False)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"试卷类型", null=True, editable=False)
+    duration = models.IntegerField(verbose_name=u'时长')
+    exam_time = models.DateTimeField(verbose_name=u"考试时间")
+    exam_end_time = models.DateTimeField(verbose_name=u"考试结束时间", null=True, editable=False)
+    exampaper = models.ForeignKey(ExamPaper, verbose_name=u"试卷", on_delete=models.PROTECT, null=True, editable=True)
+    desc = models.TextField(verbose_name=u"备注", null=True, blank=True)
+
+    examinee_count = models.IntegerField(verbose_name=u'考试人数', default=0, editable=False)
+
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'添加人', editable=False, on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"添加时间", default=timezone.now, editable=False)
+    delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
+
     class Meta:
         db_table = "exam"
         ordering = ['-id']
         verbose_name = u"考试管理"
         default_permissions = ()
 
+    @staticmethod
+    def getById(id):
+        instance = Exam.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的考试')
+        return instance
+
+    def allocation_exampaper(self, type, exampaper_instance):
+        self.type = type
+        self.subject_id = exampaper_instance.subject_id
+        self.exampaper = exampaper_instance
+
+        ExamLog.objects.filter(exam=self, delete=False).update(exampaper_id=exampaper_instance.id)
+
+    def change_examtime(self):
+        ExamLog.objects.filter(exam=self, delete=False).update(exam_time=self.exam_time)
+
+    def change_examinee(self, new_user_ids):
+        old_user_ids = ExamLog.objects.filter(exam=self, delete=False).values_list('user_id', flat=True)
+        user_adds = [val for val in new_user_ids if val not in old_user_ids]
+        user_deletes = [val for val in old_user_ids if val not in new_user_ids]
+        if user_deletes:
+            self._remove_examinee(user_deletes)
+        if user_adds:
+            self._add_examinee(user_adds)
+        self.examinee_count = ExamLog.objects.filter(exam=self, delete=False).count()
+
+    def _remove_examinee(self, user_ids):
+        ExamLog.objects.filter(exam=self, user_id__in=user_ids).update(delete=True)
+
+    def _add_examinee(self, user_ids):
+        data = []
+        for user_id in user_ids:
+            item = ExamLog(
+                type=ExamLog.FORMAL,
+                exam=self,
+                exampaper=self.exampaper,
+                user_id=user_id,
+                exam_time=self.exam_time
+            )
+            data.append(item)
+
+        ExamLog.objects.bulk_create(data)
+
 class ExamLog(models.Model):
+    MOCK = 1
+    FORMAL = 2
+    TYPE_CHOICES = (
+        (MOCK, u'模拟考试'),
+        (FORMAL, u'正式考试'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类型")
+    exam = models.ForeignKey(Exam, verbose_name=u"考试", null=True, blank=True, on_delete=models.PROTECT)
+    exampaper = models.ForeignKey(ExamPaper, verbose_name=u"试卷", null=True, blank=True, on_delete=models.PROTECT)
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'参加人员', on_delete=models.PROTECT)
+
+    scores = models.IntegerField(verbose_name=u'分数', null=True, blank=True)
+    rank = models.IntegerField(verbose_name=u'排名', null=True, blank=True)
+    use_time = models.IntegerField(verbose_name=u'用时', null=True, blank=True)
+    exam_time = models.DateTimeField(verbose_name=u"考试时间", null=True, blank=True)
+    submit_time = models.DateTimeField(verbose_name=u"交卷时间", null=True, blank=True)
+
+    single_answer_scores = models.IntegerField(verbose_name=u'单选题得分', default=0, editable=False)
+    multiple_answer_scores = models.IntegerField(verbose_name=u'多选题得分', default=0, editable=False)
+    fill_answer_scores = models.IntegerField(verbose_name=u'填空题得分', default=0, editable=False)
+    judgment_answer_scores = models.IntegerField(verbose_name=u'判断题得分', default=0, editable=False)
+
+    single_answer_count = models.IntegerField(verbose_name=u'单选题答对数', default=0, editable=False)
+    multiple_answer_count = models.IntegerField(verbose_name=u'多选题答对数', default=0, editable=False)
+    fill_answer_count = models.IntegerField(verbose_name=u'填空题答对数', default=0, editable=False)
+    judgment_answer_count = models.IntegerField(verbose_name=u'判断题答对数', default=0, editable=False)
+    delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
+
     class Meta:
         db_table = "exam_log"
         ordering = ['-id']
         verbose_name = u"考试记录"
-        default_permissions = ()
+        default_permissions = ()
+
+class ExamAnswerLog(models.Model):
+    RIGHT = 1
+    WRONG = 2
+    NOTDONE = 3
+    STATUS_CHOICES = (
+        (RIGHT, u'正确'),
+        (WRONG, u'错误'),
+        (NOTDONE, u'未做'),
+    )
+    STATUS_JSON = [{'id': item[0], 'value': item[1]} for item in STATUS_CHOICES]
+
+    main = models.ForeignKey(ExamLog, verbose_name=u"考试记录", on_delete=models.PROTECT)
+    detail = models.ForeignKey(ExamPaperDetail, verbose_name=u"试题", on_delete=models.PROTECT)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u'回答状态', default=NOTDONE)
+
+    class Meta:
+        db_table = "exam_answer_log"
+        ordering = ['id']
+        verbose_name = u"考试答题记录"
+        default_permissions = ()
+
+class ExamAnswerOptionLog(models.Model):
+    main = models.ForeignKey(ExamAnswerLog, verbose_name=u"考试答题记录", on_delete=models.PROTECT)
+    option = models.ForeignKey(ExamQuestionOption, verbose_name=u"选项", on_delete=models.PROTECT)
+
+    class Meta:
+        db_table = "exam_answer_option_log"
+        ordering = ['id']
+        verbose_name = u"选择题回答"
+        default_permissions = ()
+
+class ExamAnswerFillLog(models.Model):
+    main = models.ForeignKey(ExamAnswerLog, verbose_name=u"考试答题记录", on_delete=models.PROTECT)
+    content = models.TextField(verbose_name=u"内容")
+    order = models.IntegerField(verbose_name=u'排序')
+
+    class Meta:
+        db_table = "exam_answer_fill_log"
+        ordering = ['order', 'id']
+        verbose_name = u"填空题回答"
+        default_permissions = ()

+ 175 - 0
apps/examination/exam/serializers.py

@@ -0,0 +1,175 @@
+# coding=utf-8
+import datetime
+from django.utils import timezone
+from rest_framework import serializers
+from .models import Exam, ExamLog, ExamAnswerLog, ExamAnswerOptionLog, ExamAnswerFillLog
+from apps.examination.examquestion.models import ExamQuestion
+from utils.exceptions import CustomError
+
+
+class ExamSerializer(serializers.ModelSerializer):
+    subject_name = serializers.CharField(source='subject.name', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    question_total_scores = serializers.IntegerField(source='exampaper.question_total_scores', read_only=True)
+    passline = serializers.IntegerField(source='exampaper.passline', read_only=True)
+    exampaper_name = serializers.CharField(source='exampaper.name', read_only=True)
+    examinee_name = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Exam
+        fields = "__all__"
+
+    def get_examinee_name(self, obj):
+        examinees = ExamLog.objects.filter(exam=obj, delete=False).values('user__name')
+        examinee_list = []
+        for item in examinees:
+            if item['user__name']:
+                examinee_list.append(item['user__name'])
+        if examinee_list:
+            return ','.join(examinee_list)
+        return ''
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        exam_end_time = validated_data['exam_time'] +  datetime.timedelta(minutes=validated_data['duration'])
+        validated_data['exam_end_time'] = exam_end_time
+        instance = super(ExamSerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        if instance.delete:
+            raise CustomError(u'考试[%s]已经被删除,禁止操作' % instance.name)
+        if timezone.now() > instance.exam_time:
+            raise CustomError(u'考试已经开始,禁止修改!')
+        exam_end_time = validated_data['exam_time'] +  datetime.timedelta(minutes=validated_data['duration'])
+        validated_data['exam_end_time'] = exam_end_time
+        instance = super(ExamSerializer, self).update(instance, validated_data)
+        instance.change_examtime()
+        instance.save()
+        return instance
+
+
+class FormalExamLogSerializer(serializers.ModelSerializer):
+    exam_name = serializers.CharField(source='exam.name', read_only=True)
+    exam_subject_name = serializers.CharField(source='exam.subject.name', read_only=True)
+    user_name = serializers.CharField(source='user.name', read_only=True)
+    user_department = serializers.CharField(source='user.department.name', read_only=True)
+    question_total_scores = serializers.IntegerField(source='exampaper.question_total_scores', read_only=True)
+    passline = serializers.IntegerField(source='exampaper.passline', read_only=True)
+    single_count = serializers.IntegerField(source='exampaper.single_total_count', read_only=True)
+    single_scores = serializers.IntegerField(source='exampaper.single_total_scores', read_only=True)
+    multiple_count = serializers.IntegerField(source='exampaper.multiple_total_count', read_only=True)
+    multiple_scores = serializers.IntegerField(source='exampaper.multiple_total_scores', read_only=True)
+    fill_count = serializers.IntegerField(source='exampaper.fill_total_count', read_only=True)
+    fill_scores = serializers.IntegerField(source='exampaper.fill_total_scores', read_only=True)
+    judgment_count = serializers.IntegerField(source='exampaper.judgment_total_count', read_only=True)
+    judgment_scores = serializers.IntegerField(source='exampaper.judgment_total_scores', read_only=True)
+
+    class Meta:
+        model = ExamLog
+        fields = "__all__"
+
+
+class StaffExamLogSerializer(serializers.ModelSerializer):
+    exampaper_name = serializers.CharField(source='exampaper.name', read_only=True)
+    right_count = serializers.SerializerMethodField()
+    wrong_count = serializers.SerializerMethodField()
+
+    class Meta:
+        model = ExamLog
+        fields = ('id', 'type', 'exampaper', 'user', 'exam_time', 'exampaper_name', 'exam_time', 'submit_time',
+                  'scores', 'right_count','wrong_count','rank',)
+
+    def get_right_count(self, obj):
+        return obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count
+
+    def get_wrong_count(self, obj):
+        return obj.exampaper.question_total_count - (obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count)
+
+class StaffExamLogRetrieveSerializer(serializers.ModelSerializer):
+    exampaper_name = serializers.CharField(source='exampaper.name', read_only=True)
+    question_total_count = serializers.CharField(source='exampaper.question_total_count', read_only=True)
+    question_total_scores = serializers.CharField(source='exampaper.question_total_scores', read_only=True)
+
+    total_right_count = serializers.SerializerMethodField()
+    answer_items = serializers.SerializerMethodField()
+
+    class Meta:
+        model = ExamLog
+        fields = "__all__"
+
+    def get_total_right_count(self, obj):
+        return obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count
+
+    def get_answer_items(self, obj):
+        data = []
+        if obj.exampaper.single_total_count:
+            item = {
+                'name': '单选题',
+                'total_count': obj.exampaper.single_total_count,
+                'total_right': obj.single_answer_count,
+                'total_scores': obj.exampaper.single_total_scores,
+                'answer_scores': obj.single_answer_scores,
+            }
+            data.append(item)
+        if obj.exampaper.multiple_total_count:
+            item = {
+                'name': '多选题',
+                'total_count': obj.exampaper.multiple_total_count,
+                'total_right': obj.multiple_answer_count,
+                'total_scores': obj.exampaper.multiple_total_scores,
+                'answer_scores': obj.multiple_answer_scores,
+            }
+            data.append(item)
+        if obj.exampaper.fill_total_count:
+            item = {
+                'name': '填空题',
+                'total_count': obj.exampaper.fill_total_count,
+                'total_right': obj.fill_answer_count,
+                'total_scores': obj.exampaper.fill_total_scores,
+                'answer_scores': obj.fill_answer_scores,
+            }
+            data.append(item)
+        if obj.exampaper.judgment_total_count:
+            item = {
+                'name': '判断题',
+                'total_count': obj.exampaper.judgment_total_count,
+                'total_right': obj.judgment_answer_count,
+                'total_scores': obj.exampaper.judgment_total_scores,
+                'answer_scores': obj.judgment_answer_scores,
+            }
+            data.append(item)
+        return data
+
+
+class ExamAnswerLogSimpleSerializer(serializers.ModelSerializer):
+    item = serializers.SerializerMethodField()
+
+    class Meta:
+        model = ExamAnswerLog
+        fields = ('item', )
+
+    def get_item(self, obj):
+        if obj.detail.question.type <= ExamQuestion.MULTIPLE:
+            rows = ExamAnswerOptionLog.objects.filter(main=obj)
+            return ExamAnswerOptionLogSimpleSerializer(rows, many=True).data
+        elif obj.detail.question.type == ExamQuestion.FILL:
+            rows = ExamAnswerFillLog.objects.filter(main=obj)
+            return ExamAnswerFillLogSimpleSerializer(rows, many=True).data
+        else:
+            return []
+
+
+class ExamAnswerOptionLogSimpleSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = ExamAnswerOptionLog
+        fields = ('option', )
+
+
+class ExamAnswerFillLogSimpleSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = ExamAnswerFillLog
+        fields = ('content', 'order', )

+ 23 - 0
apps/examination/exampaper/filters.py

@@ -0,0 +1,23 @@
+# coding=utf-8
+import django_filters
+
+from .models import ExamPaper
+
+class ExamPaperFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
+    subject = django_filters.CharFilter(field_name='subject_id')
+    type = django_filters.CharFilter(field_name='type')
+    status = django_filters.CharFilter(method='status_filter')
+
+    def status_filter(self, queryset, name,value):
+        from apps.examination.exam.models import ExamLog
+        exampaper_ids = ExamLog.objects.filter(user=self.request.user, delete=False).values_list('exampaper_id', flat=True)
+        if value == '1':
+            queryset = queryset.filter(id__in=exampaper_ids)
+        elif value == '2':
+            queryset = queryset.exclude(id__in=exampaper_ids)
+        return queryset
+
+    class Meta:
+        model = ExamPaper
+        fields = "__all__"

+ 164 - 0
apps/examination/exampaper/models.py

@@ -1,10 +1,174 @@
 # coding=utf-8
 
+import random
 from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+from utils.exceptions import CustomError
+
+from apps.foundation.models import Subject
+from apps.examination.examquestion.models import ExamQuestion
 
 class ExamPaper(models.Model):
+    MOCK = 1
+    FORMAL = 2
+    RANDOM = 3
+    TYPE_CHOICES = (
+        (MOCK, u'模拟试卷'),
+        (FORMAL, u'正式试卷'),
+        (RANDOM, u'随机试卷'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    name = models.CharField(max_length=200, verbose_name=u"名称")
+    subject = models.ForeignKey(Subject, verbose_name=u"科目", on_delete=models.PROTECT)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类型")
+    passline = models.IntegerField(verbose_name=u'及格线')
+    desc = models.TextField(verbose_name=u"备注", null=True, blank=True)
+
+    single_simple_count = models.IntegerField(verbose_name=u'简单单选题数量', default=0)
+    multiple_simple_count = models.IntegerField(verbose_name=u'简单多选题数量', default=0)
+    fill_simple_count = models.IntegerField(verbose_name=u'简单填空题数量', default=0)
+    judgment_simple_count = models.IntegerField(verbose_name=u'简单判断题数量', default=0)
+    single_mid_count = models.IntegerField(verbose_name=u'中等单选题数量', default=0)
+    multiple_mid_count = models.IntegerField(verbose_name=u'中等多选题数量', default=0)
+    fill_mid_count = models.IntegerField(verbose_name=u'中等填空题数量', default=0)
+    judgment_mid_count = models.IntegerField(verbose_name=u'中等判断题数量', default=0)
+    single_hard_count = models.IntegerField(verbose_name=u'困难单选题数量', default=0)
+    multiple_hard_count = models.IntegerField(verbose_name=u'困难多选题数量', default=0)
+    fill_hard_count = models.IntegerField(verbose_name=u'困难填空题数量', default=0)
+    judgment_hard_count = models.IntegerField(verbose_name=u'困难判断题数量', default=0)
+
+    single_scores = models.IntegerField(verbose_name=u'单选题单题分数', default=0)
+    multiple_scores = models.IntegerField(verbose_name=u'多选题单题分数', default=0)
+    fill_scores = models.IntegerField(verbose_name=u'填空题单题分数', default=0)
+    judgment_scores = models.IntegerField(verbose_name=u'判断题单题分数', default=0)
+
+    single_total_count = models.IntegerField(verbose_name=u'单选题总数量', default=0, editable=False)
+    multiple_total_count = models.IntegerField(verbose_name=u'多选题总数量', default=0, editable=False)
+    fill_total_count = models.IntegerField(verbose_name=u'填空题总数量', default=0, editable=False)
+    judgment_total_count = models.IntegerField(verbose_name=u'判断题总数量', default=0, editable=False)
+
+    single_total_scores = models.IntegerField(verbose_name=u'单选题总分数', default=0, editable=False)
+    multiple_total_scores = models.IntegerField(verbose_name=u'多选题总分数', default=0, editable=False)
+    fill_total_scores = models.IntegerField(verbose_name=u'填空题总分数', default=0, editable=False)
+    judgment_total_scores = models.IntegerField(verbose_name=u'判断题总分数', default=0, editable=False)
+
+    question_total_count  = models.IntegerField(verbose_name=u'试题总数量', default=0, editable=False)
+    question_total_scores = models.IntegerField(verbose_name=u'试题总分数', default=0, editable=False)
+
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'添加人', editable=False, on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"添加时间", default=timezone.now, editable=False)
+    delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
+
+    did_count = models.IntegerField(verbose_name=u'做过人数', default=0, editable=False)
+
     class Meta:
         db_table = "exam_paper"
         ordering = ['-id']
         verbose_name = u"试卷管理"
         default_permissions = ()
+
+    @staticmethod
+    def getById(id):
+        instance = ExamPaper.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的试卷')
+        return instance
+
+    def generate_passline(self):
+        self.passline = int(self.question_total_scores * 0.6)
+
+    def clear_detail(self):
+        ExamPaperDetail.objects.filter(main=self).update(delete=True)
+
+    def generate_detail(self):
+        begin_order = 1
+        if self.single_simple_count:
+            self._generate_detail(self.single_simple_count, ExamQuestion.SINGLE, ExamQuestion.SIMPLE, begin_order)
+            begin_order += self.single_simple_count
+        if self.single_mid_count:
+            self._generate_detail(self.single_mid_count, ExamQuestion.SINGLE, ExamQuestion.MID, begin_order)
+            begin_order += self.single_mid_count
+        if self.single_hard_count:
+            self._generate_detail(self.single_hard_count, ExamQuestion.SINGLE, ExamQuestion.HARD, begin_order)
+            begin_order += self.single_hard_count
+        if self.multiple_simple_count:
+            self._generate_detail(self.multiple_simple_count, ExamQuestion.MULTIPLE, ExamQuestion.SIMPLE, begin_order)
+            begin_order += self.multiple_simple_count
+        if self.multiple_mid_count:
+            self._generate_detail(self.multiple_mid_count, ExamQuestion.MULTIPLE, ExamQuestion.MID, begin_order)
+            begin_order += self.multiple_mid_count
+        if self.multiple_hard_count:
+            self._generate_detail(self.multiple_hard_count, ExamQuestion.MULTIPLE, ExamQuestion.HARD, begin_order)
+            begin_order += self.multiple_hard_count
+        if self.fill_simple_count:
+            self._generate_detail(self.fill_simple_count, ExamQuestion.FILL, ExamQuestion.SIMPLE, begin_order)
+            begin_order += self.fill_simple_count
+        if self.fill_mid_count:
+            self._generate_detail(self.fill_mid_count, ExamQuestion.FILL, ExamQuestion.MID, begin_order)
+            begin_order += self.fill_mid_count
+        if self.fill_hard_count:
+            self._generate_detail(self.fill_hard_count, ExamQuestion.FILL, ExamQuestion.HARD, begin_order)
+            begin_order += self.fill_hard_count
+        if self.judgment_simple_count:
+            self._generate_detail(self.judgment_simple_count, ExamQuestion.JUDGMENT, ExamQuestion.SIMPLE, begin_order)
+            begin_order += self.judgment_simple_count
+        if self.judgment_mid_count:
+            self._generate_detail(self.judgment_mid_count, ExamQuestion.JUDGMENT, ExamQuestion.MID, begin_order)
+            begin_order += self.judgment_mid_count
+        if self.judgment_hard_count:
+            self._generate_detail(self.judgment_hard_count, ExamQuestion.JUDGMENT, ExamQuestion.HARD, begin_order)
+            begin_order += self.judgment_hard_count
+
+    def update_count(self):
+        self.single_total_count = self.single_simple_count + self.single_mid_count + self.single_hard_count
+        self.multiple_total_count = self.multiple_simple_count + self.multiple_mid_count + self.multiple_hard_count
+        self.fill_total_count = self.fill_simple_count + self.fill_mid_count + self.fill_hard_count
+        self.judgment_total_count = self.judgment_simple_count + self.judgment_mid_count + self.judgment_hard_count
+        self.single_total_scores = self.single_scores * self.single_total_count
+        self.multiple_total_scores = self.multiple_scores * self.multiple_total_count
+        self.fill_total_scores = self.fill_scores * self.fill_total_count
+        self.judgment_total_scores = self.judgment_scores * self.judgment_total_count
+        self.question_total_count = self.single_total_count + self.multiple_total_count + self.fill_total_count + self.judgment_total_count
+        self.question_total_scores = self.single_total_scores + self.multiple_total_scores + self.fill_total_scores + self.judgment_total_scores
+        return self
+
+    def _generate_detail(self, count, type, difficulty, begin_order):
+        questions = ExamQuestion.objects.filter(
+            chapter__subject=self.subject,
+            difficulty = difficulty,
+            type = type,
+            delete=False
+        )
+        questions_count = questions.count()
+        if questions_count < count:
+            raise CustomError(u'[%s][%s]数量不足!' % (ExamQuestion.DIFFICULTY_CHOICES[difficulty-1][1], ExamQuestion.TYPE_CHOICES[type-1][1]))
+        question_ids = questions.values_list('id', flat=True)
+        rows = random.sample(list(question_ids), count)
+        random.shuffle(rows)
+
+        data = []
+        for row in rows:
+            item = ExamPaperDetail(
+                main=self,
+                question_id=row,
+                order=begin_order
+            )
+            begin_order += 1
+            data.append(item)
+
+        ExamPaperDetail.objects.bulk_create(data)
+
+class ExamPaperDetail(models.Model):
+    main = models.ForeignKey(ExamPaper, verbose_name=u"试卷", on_delete=models.PROTECT)
+    question = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
+    order = models.IntegerField(verbose_name=u'序号', editable=False)
+    delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
+
+    class Meta:
+        db_table = "exam_paper_detail"
+        ordering = ['order']
+        verbose_name = u"试卷明细"
+        default_permissions = ()

+ 98 - 0
apps/examination/exampaper/serializers.py

@@ -0,0 +1,98 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from .models import ExamPaper
+from utils.exceptions import CustomError
+
+class ExamPaperSerializer(serializers.ModelSerializer):
+    subject_name = serializers.CharField(source='subject.name', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+
+    class Meta:
+        model = ExamPaper
+        fields = "__all__"
+
+    def validate(self, attrs):
+        if 'subject' in attrs and attrs['subject'].delete:
+            raise CustomError(u'科目[%s]已被删除!' % attrs['subject'].name)
+        return attrs
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        instance = super(ExamPaperSerializer, self).create(validated_data)
+        instance.update_count()
+        if not instance.question_total_count:
+            raise CustomError(u'请选择试题!')
+        if instance.passline > instance.question_total_scores:
+           raise CustomError(u'及格线不能大于试题总分!')
+        instance.save()
+
+        instance.generate_detail()
+        return instance
+
+    def update(self, instance, validated_data):
+        old_count = []
+        old_count.append(instance.single_simple_count)
+        old_count.append(instance.single_mid_count)
+        old_count.append(instance.single_hard_count)
+        old_count.append(instance.multiple_simple_count)
+        old_count.append(instance.multiple_mid_count)
+        old_count.append(instance.multiple_hard_count)
+        old_count.append(instance.fill_simple_count)
+        old_count.append(instance.fill_mid_count)
+        old_count.append(instance.fill_hard_count)
+        old_count.append(instance.judgment_simple_count)
+        old_count.append(instance.judgment_mid_count)
+        old_count.append(instance.judgment_hard_count)
+
+        if instance.delete:
+            raise CustomError(u'试卷[%s]已经被删除,禁止操作' % instance.name)
+        instance = super(ExamPaperSerializer, self).update(instance, validated_data)
+        instance.update_count()
+        if not instance.question_total_count:
+            raise CustomError(u'请选择试题!')
+        if instance.passline > instance.question_total_scores:
+           raise CustomError(u'及格线不能大于试题总分!')
+        instance.save()
+
+        new_count = []
+        new_count.append(instance.single_simple_count)
+        new_count.append(instance.single_mid_count)
+        new_count.append(instance.single_hard_count)
+        new_count.append(instance.multiple_simple_count)
+        new_count.append(instance.multiple_mid_count)
+        new_count.append(instance.multiple_hard_count)
+        new_count.append(instance.fill_simple_count)
+        new_count.append(instance.fill_mid_count)
+        new_count.append(instance.fill_hard_count)
+        new_count.append(instance.judgment_simple_count)
+        new_count.append(instance.judgment_mid_count)
+        new_count.append(instance.judgment_hard_count)
+
+        change_detail = False
+        for i in range(0, len(old_count)):
+            if old_count[i] != new_count[i]:
+                change_detail = True
+                break
+
+        if change_detail:
+            instance.clear_detail()
+            instance.generate_detail()
+
+        return instance
+
+class StaffExamPaperSerializer(serializers.ModelSerializer):
+    subject_name = serializers.CharField(source='subject.name', read_only=True)
+    status_text = serializers.SerializerMethodField()
+
+    def get_status_text(self, obj):
+        from apps.examination.exam.models import ExamLog
+        exampaper = ExamLog.objects.filter(user=self.context['request'].user,exampaper=obj)
+        if exampaper:
+            return True
+        return False
+
+    class Meta:
+        model = ExamPaper
+        fields = ('id', 'name','subject_name','status_text','did_count','question_total_count', )

+ 25 - 0
apps/examination/examquestion/filters.py

@@ -0,0 +1,25 @@
+# coding=utf-8
+import django_filters
+
+from .models import *
+
+class ExamQuestionFilter(django_filters.FilterSet):
+    chapter = django_filters.CharFilter(field_name='chapter_id')
+    subject = django_filters.CharFilter(field_name='chapter__subject_id')
+    type = django_filters.CharFilter(field_name='type')
+    title = django_filters.CharFilter(field_name='title', lookup_expr='icontains')
+
+    class Meta:
+        model = ExamQuestion
+        fields = '__all__'
+
+class ExamQuestionFeedbackFilter(django_filters.FilterSet):
+    chapter = django_filters.CharFilter(field_name='main__chapter_id')
+    subject = django_filters.CharFilter(field_name='main__chapter__subject_id')
+    status = django_filters.CharFilter(field_name='status')
+    type = django_filters.CharFilter(field_name='type')
+    title = django_filters.CharFilter(field_name='main__title', lookup_expr='icontains')
+
+    class Meta:
+        model = ExamQuestionFeedback
+        fields = '__all__'

+ 105 - 1
apps/examination/examquestion/models.py

@@ -1,17 +1,121 @@
 # coding=utf-8
 
 from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+from utils.exceptions import CustomError
+
+from apps.foundation.models import Chapter
 
 class ExamQuestion(models.Model):
+    SINGLE = 1
+    MULTIPLE = 2
+    FILL = 3
+    JUDGMENT = 4
+    TYPE_CHOICES = (
+        (SINGLE, u'单选题'),
+        (MULTIPLE, u'多选题'),
+        (FILL, u'填空题'),
+        (JUDGMENT, u'判断题'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    SIMPLE = 1
+    MID = 2
+    HARD = 3
+    DIFFICULTY_CHOICES = (
+        (SIMPLE, u'简单'),
+        (MID, u'中等'),
+        (HARD, u'困难'),
+    )
+    DIFFICULTY_JSON = [{'id': item[0], 'value': item[1]} for item in DIFFICULTY_CHOICES]
+
+    chapter = models.ForeignKey(Chapter, verbose_name=u"章节", on_delete=models.PROTECT)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"题型")
+    difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, verbose_name=u"难度")
+    scores = models.IntegerField(verbose_name=u'分数')
+    title = models.TextField(verbose_name=u"题目")
+    judgment = models.BooleanField(verbose_name=u'判断题答案', default=False)
+    analysis = models.TextField(verbose_name=u"解析", null=True, blank=True)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'录入人',related_name='exam_question_user', editable=False, on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"录入时间", default=timezone.now, editable=False)
+    delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
+
     class Meta:
         db_table = "exam_question"
         ordering = ['-id']
         verbose_name = u"试题管理"
         default_permissions = ()
 
+class ExamQuestionOption(models.Model):
+    main = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
+    content = models.TextField(verbose_name=u"内容")
+    right = models.BooleanField(verbose_name=u'正确答案', default=False)
+    delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
+
+    class Meta:
+        db_table = "exam_question_option"
+        ordering = ['id']
+        verbose_name = u"选择题选项" # 单选、多选
+        default_permissions = ()
+
+class ExamQuestionFill(models.Model):
+    main = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
+    content = models.TextField(verbose_name=u"内容")
+    order = models.IntegerField(verbose_name=u'排序')
+    delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
+
+    class Meta:
+        db_table = "exam_question_fill"
+        ordering = ['order', 'id']
+        verbose_name = u"试题填空"
+        default_permissions = ()
+
 class ExamQuestionFeedback(models.Model):
+    TYPE = 1
+    ANSWER = 2
+    ANALYSIS = 3
+    OTHER = 4
+    TYPE_CHOICES = (
+        (TYPE, u'题目类型'),
+        (ANSWER, u'题目答案'),
+        (ANALYSIS, u'题目解析'),
+        (OTHER, u'其它'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    UNTREATED = 1
+    PROCESSED = 2
+    STATUS_CHOICES = (
+        (UNTREATED, u'未处理'),
+        (PROCESSED, u'已处理'),
+    )
+    STATUS_JSON = [{'id': item[0], 'value': item[1]} for item in STATUS_CHOICES]
+
+    main = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"错误类型")
+    desc = models.TextField(verbose_name=u"错误描述", null=True, blank=True)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'反馈人',related_name='exam_question_feedback_user', editable=False, on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"反馈时间", default=timezone.now, editable=False)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u'处理状态', default=UNTREATED)
+    process_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u"处理人", on_delete=models.PROTECT, null=True, related_name="exam_question_feedback_ref_process_user")
+    process_time = models.DateTimeField(verbose_name=u'处理时间', null=True)
+
     class Meta:
         db_table = "exam_question_feedback"
         ordering = ['-id']
         verbose_name = u"错误反馈"
-        default_permissions = ()
+        default_permissions = ()
+
+    @staticmethod
+    def getById(id):
+        instance = ExamQuestionFeedback.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的错误反馈')
+        return instance
+
+    def process(self, user):
+        self.process_user = user
+        self.status = ExamQuestionFeedback.PROCESSED
+        self.process_time = timezone.now()

+ 164 - 0
apps/examination/examquestion/serializers.py

@@ -0,0 +1,164 @@
+# coding=utf-8
+import json
+from rest_framework import serializers
+from utils.exceptions import CustomError
+from .models import *
+
+
+class ExamQuestionSerializer(serializers.ModelSerializer):
+    subject = serializers.CharField(source='chapter.subject.id', read_only=True)
+    subject_text = serializers.CharField(source='chapter.subject.name', read_only=True)
+    chapter_text = serializers.CharField(source='chapter.name', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+    difficulty_text = serializers.CharField(source='get_difficulty_display', read_only=True)
+    judgment = serializers.SerializerMethodField()
+    items = serializers.SerializerMethodField()
+
+    def get_items(self, obj):
+        if obj.type <= ExamQuestion.MULTIPLE:
+            rows = ExamQuestionOption.objects.filter(main=obj, delete=False)
+            return ExamQuestionOptionSerializer(rows, many=True).data
+        elif obj.type == ExamQuestion.FILL:
+            rows = ExamQuestionFill.objects.filter(main=obj, delete=False)
+            return ExamQuestionFillSerializer(rows, many=True).data
+        else:
+            return []
+
+    def get_judgment(self, obj):
+        if obj.judgment:
+            return "1"
+        return "0"
+
+    class Meta:
+        model = ExamQuestion
+        fields = '__all__'
+
+    def create(self, validated_data):
+        if 'judgment' in self.initial_data:
+            validated_data['judgment'] = True if self.initial_data['judgment'] == '1' else False
+        validated_data['create_user'] = self.context['request'].user
+        instance = super(ExamQuestionSerializer, self).create(validated_data)
+        rows = json.loads(self.initial_data['rows'])
+
+        for row in rows:
+            if not row['content']:
+                raise CustomError('答案不能为空,请填写答案!')
+            if instance.type == ExamQuestion.SINGLE or instance.type == ExamQuestion.MULTIPLE:
+                ExamQuestionOption.objects.create(
+                    main=instance,
+                    content=row['content'],
+                    right=row['right'],
+                )
+            elif instance.type == ExamQuestion.FILL:
+                ExamQuestionFill.objects.create(
+                    main=instance,
+                    content=row['content'],
+                    order=row['order'] + 1,
+                )
+        return instance
+
+    def update(self, instance, validated_data):
+        if 'judgment' in self.initial_data:
+            validated_data['judgment'] = True if self.initial_data['judgment'] == '1' else False
+        instance = super(ExamQuestionSerializer, self).update(instance, validated_data)
+        rows = json.loads(self.initial_data['rows'])
+        ExamQuestionOption.objects.filter(main=instance).update(delete=True, right=False)
+        ExamQuestionFill.objects.filter(main=instance).update(delete=True)
+        for row in rows:
+            if not row['content']:
+                raise CustomError('答案不能为空,请填写答案!')
+            if instance.type <= ExamQuestion.MULTIPLE:
+                if row['id']:
+                    # 更新
+                    ExamQuestionOption.objects.filter(main=instance, id=row['id']).update(delete=False,
+                                                                                          content=row['content'],
+                                                                                          right=row['right'])
+                else:
+                    # 新增
+                    ExamQuestionOption.objects.create(
+                        main=instance,
+                        content=row['content'],
+                        right=row['right'],
+                    )
+            elif instance.type == ExamQuestion.FILL:
+                if row['id']:
+                    # 更新
+                    ExamQuestionFill.objects.filter(main=instance, id=row['id']).update(delete=False,
+                                                                                        content=row['content'],
+                                                                                        order=row['order']+1)
+                else:
+                    # 新增
+                    ExamQuestionFill.objects.create(
+                        main=instance,
+                        content=row['content'],
+                        order=row['order'] + 1,
+                    )
+        return instance
+
+
+class ExamQuestionOptionSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = ExamQuestionOption
+        fields = '__all__'
+
+
+class ExamQuestionFillSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = ExamQuestionFill
+        fields = '__all__'
+
+class ExamQuestionFeedbackSerializer(serializers.ModelSerializer):
+    question_type_text = serializers.CharField(source='main.get_type_display', read_only=True)
+    question_title = serializers.CharField(source='main.title', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    title = serializers.CharField(source='main.title', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+
+    class Meta:
+        model = ExamQuestionFeedback
+        fields = "__all__"
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        instance = super(ExamQuestionFeedbackSerializer, self).create(validated_data)
+        return instance
+
+
+class ExamQuestionSimpleSerializer(serializers.ModelSerializer):
+    judgment = serializers.SerializerMethodField()
+    items = serializers.SerializerMethodField()
+
+    def get_items(self, obj):
+        if obj.type <= ExamQuestion.MULTIPLE:
+            rows = ExamQuestionOption.objects.filter(main=obj, delete=False)
+            return ExamQuestionOptionSimpleSerializer(rows, many=True).data
+        elif obj.type == ExamQuestion.FILL:
+            rows = ExamQuestionFill.objects.filter(main=obj, delete=False)
+            return ExamQuestionFillSimpleSerializer(rows, many=True).data
+        else:
+            return []
+
+    def get_judgment(self, obj):
+        if obj.judgment:
+            return "1"
+        return "0"
+
+    class Meta:
+        model = ExamQuestion
+        fields = ('title', 'judgment', 'items', 'type', 'analysis', )
+
+
+class ExamQuestionOptionSimpleSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = ExamQuestionOption
+        fields = ('content', 'right', 'id', )
+
+
+class ExamQuestionFillSimpleSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = ExamQuestionFill
+        fields = ('content', 'order', )

+ 10 - 0
apps/examination/urls.py

@@ -0,0 +1,10 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+urlpatterns = [
+    url(r'^examquestion/', include('apps.examination.examquestion.urls')),
+]
+
+router = SimpleRouter()
+urlpatterns += router.urls

+ 21 - 0
apps/foundation/filters.py

@@ -0,0 +1,21 @@
+# coding=utf-8
+import django_filters
+
+from .models import Subject, Chapter
+
+
+class SubjectFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
+
+    class Meta:
+        model = Subject
+        fields = "__all__"
+
+
+class ChapterFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
+    subject_name = django_filters.CharFilter(field_name="subject__name", lookup_expr="icontains")
+
+    class Meta:
+        model = Chapter
+        fields = "__all__"

+ 12 - 1
apps/foundation/models.py

@@ -2,16 +2,27 @@
 
 from django.db import models
 
+
 class Subject(models.Model):
+    name = models.CharField(max_length=100, verbose_name=u"名称")
+    notes = models.CharField(max_length=500, verbose_name=u"备注", blank=True, null=True)
+    delete = models.BooleanField(verbose_name=u'删除', default=False)
+
     class Meta:
         db_table = "base_subject"
         ordering = ['-id']
         verbose_name = u"科目章节设置"
         default_permissions = ()
 
+
 class Chapter(models.Model):
+    subject = models.ForeignKey(Subject, verbose_name=u"科目", on_delete=models.PROTECT)
+    name = models.CharField(max_length=100, verbose_name=u"名称")
+    notes = models.CharField(max_length=500, verbose_name=u"备注", blank=True, null=True)
+    delete = models.BooleanField(verbose_name=u'删除', default=False)
+
     class Meta:
         db_table = "base_chapter"
         ordering = ['-id']
         verbose_name = u"章节"
-        default_permissions = ()
+        default_permissions = ()

+ 55 - 0
apps/foundation/serializers.py

@@ -0,0 +1,55 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from .models import Subject, Chapter
+from utils.exceptions import CustomError
+
+
+class SubjectSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Subject
+        fields = "__all__"
+
+    def create(self, validated_data):
+        instance = super(SubjectSerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        instance = super(SubjectSerializer, self).update(instance, validated_data)
+        return instance
+
+
+class ChapterSerializer(serializers.ModelSerializer):
+    subject_name = serializers.CharField(source='subject.name', read_only=True)
+
+    class Meta:
+        model = Chapter
+        fields = "__all__"
+
+    def validate(self, attrs):
+        if 'subject' in attrs and attrs['subject'].delete:
+            raise CustomError(u'科目[%s]已被删除!' % attrs['subject'].name)
+        return attrs
+
+    def create(self, validated_data):
+        instance = super(ChapterSerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        instance = super(ChapterSerializer, self).update(instance, validated_data)
+        return instance
+
+
+class SubjectSimpleSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Subject
+        fields = ['id', 'name', ]
+
+
+class ChapterSimpleSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Chapter
+        fields = ['id', 'name', 'subject_id' ]

+ 24 - 0
apps/knowledge/filters.py

@@ -0,0 +1,24 @@
+# coding=utf-8
+import django_filters
+
+from .models import KnowledgeBase, KnowledgeBaseFeedback
+
+class KnowledgeBaseFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
+    subject = django_filters.CharFilter(field_name='chapter__subject_id')
+    chapter = django_filters.CharFilter(field_name='chapter_id')
+
+    class Meta:
+        model = KnowledgeBase
+        fields = "__all__"
+
+class KnowledgeBaseFeedbackFilter(django_filters.FilterSet):
+    chapter = django_filters.CharFilter(field_name='main__chapter_id')
+    subject = django_filters.CharFilter(field_name='main__chapter__subject_id')
+    status = django_filters.CharFilter(field_name='status')
+    type = django_filters.CharFilter(field_name='type')
+    name = django_filters.CharFilter(field_name='main__name', lookup_expr='icontains')
+
+    class Meta:
+        model = KnowledgeBaseFeedback
+        fields = '__all__'

+ 55 - 0
apps/knowledge/models.py

@@ -1,8 +1,22 @@
 # coding=utf-8
 
 from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+from utils.exceptions import CustomError
+
+from apps.foundation.models import Chapter
 
 class KnowledgeBase(models.Model):
+    chapter = models.ForeignKey(Chapter, verbose_name=u"章节", on_delete=models.PROTECT)
+    name = models.CharField(max_length=200, verbose_name=u"名称")
+    feature = models.TextField(verbose_name=u"识别特征")
+    desc = models.TextField(verbose_name=u"备注", null=True, blank=True)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'录入人',related_name='knowledge_base_user', editable=False, on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"录入时间", default=timezone.now, editable=False)
+    delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
+
     class Meta:
         db_table = "knowledge_base"
         ordering = ['-id']
@@ -10,8 +24,49 @@ class KnowledgeBase(models.Model):
         default_permissions = ()
 
 class KnowledgeBaseFeedback(models.Model):
+    NAME = 1
+    FEATURE = 2
+    IMG = 3
+    OTHER = 4
+    TYPE_CHOICES = (
+        (NAME, u'名称错误'),
+        (FEATURE, u'特征错误'),
+        (IMG, u'图片错误'),
+        (OTHER, u'其它'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    UNTREATED = 1
+    PROCESSED = 2
+    STATUS_CHOICES = (
+        (UNTREATED, u'未处理'),
+        (PROCESSED, u'已处理'),
+    )
+    STATUS_JSON = [{'id': item[0], 'value': item[1]} for item in STATUS_CHOICES]
+
+    main = models.ForeignKey(KnowledgeBase, verbose_name=u"知识", on_delete=models.PROTECT)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"错误类型")
+    desc = models.TextField(verbose_name=u"错误描述", null=True, blank=True)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'反馈人', editable=False, on_delete=models.PROTECT, related_name='knowledge_base_feedback_create_usr_id')
+    create_time = models.DateTimeField(verbose_name=u"反馈时间", default=timezone.now, editable=False)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u'处理状态', default=UNTREATED)
+    process_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u"处理人", on_delete=models.PROTECT, null=True, related_name='knowledge_base_feedback_process_usr_id')
+    process_time = models.DateTimeField(verbose_name=u'处理时间', null=True)
+
     class Meta:
         db_table = "knowledge_base_feedback"
         ordering = ['-id']
         verbose_name = u"错误反馈"
         default_permissions = ()
+
+    @staticmethod
+    def getById(id):
+        instance = KnowledgeBaseFeedback.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的错误反馈')
+        return instance
+
+    def process(self, user):
+        self.process_user = user
+        self.status = KnowledgeBaseFeedback.PROCESSED
+        self.process_time = timezone.now()

+ 43 - 0
apps/knowledge/serializers.py

@@ -0,0 +1,43 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from .models import KnowledgeBase, KnowledgeBaseFeedback
+from utils.exceptions import CustomError
+
+class KnowledgeBaseSerializer(serializers.ModelSerializer):
+    subject_name = serializers.CharField(source='chapter.subject.name', read_only=True)
+    subject = serializers.CharField(source='chapter.subject.id', read_only=True)
+    chapter_name = serializers.CharField(source='chapter.name', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+
+    class Meta:
+        model = KnowledgeBase
+        fields = "__all__"
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        instance = super(KnowledgeBaseSerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        if instance.delete:
+            raise CustomError(u'知识[%s]已经被删除,禁止操作' % instance.name)
+        instance = super(KnowledgeBaseSerializer, self).update(instance, validated_data)
+        return instance
+
+class KnowledgeBaseFeedbackSerializer(serializers.ModelSerializer):
+    subject_name = serializers.CharField(source='main.chapter.subject.name', read_only=True)
+    chapter_name = serializers.CharField(source='main.chapter.name', read_only=True)
+    name = serializers.CharField(source='main.name', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+
+    class Meta:
+        model = KnowledgeBaseFeedback
+        fields = "__all__"
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        instance = super(KnowledgeBaseFeedbackSerializer, self).create(validated_data)
+        return instance

+ 13 - 0
apps/practise/errorbook/filters.py

@@ -0,0 +1,13 @@
+# coding=utf-8
+import django_filters
+
+from .models import ErrorBook
+
+
+class ErrorBookFilter(django_filters.FilterSet):
+    subject = django_filters.CharFilter(field_name='question__chapter__subject_id')
+    sort = django_filters.OrderingFilter(fields=('last_time', 'question__chapter__subject_id', ))
+
+    class Meta:
+        model = ErrorBook
+        fields = "__all__"

+ 27 - 2
apps/practise/errorbook/models.py

@@ -1,10 +1,35 @@
 # coding=utf-8
-
+from django.db.models import F
 from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+from apps.examination.exam.models import ExamAnswerLog
+from apps.examination.examquestion.models import ExamQuestion
 
 class ErrorBook(models.Model):
+    question = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'答题人', editable=False, on_delete=models.PROTECT)
+    last_answer_log = models.ForeignKey(ExamAnswerLog, verbose_name=u"最后答题记录", on_delete=models.PROTECT)
+    last_time = models.DateTimeField(verbose_name=u"最后出错时间", default=timezone.now)
+    wrong_count = models.IntegerField(verbose_name=u'出错次数', default=0, editable=False)
+
     class Meta:
         db_table = "practise_error_book"
         ordering = ['-id']
         verbose_name = u"错题集"
-        default_permissions = ()
+        default_permissions = ()
+
+    @staticmethod
+    def add_error(question, user, answer_log):
+        '''
+        :param question: 试题对象
+        :param user: 用户对象
+        :param answer_log: 答题记录对象
+        :return:
+        '''
+        instance = ErrorBook.objects.filter(question=question, create_user=user)
+        if instance:
+            instance.update(last_answer_log=answer_log, wrong_count=F('wrong_count')+1, last_time=timezone.now())
+        else:
+            ErrorBook.objects.create(question=question, create_user=user, last_answer_log=answer_log, wrong_count=1)

+ 22 - 0
apps/practise/errorbook/serializers.py

@@ -0,0 +1,22 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from .models import ErrorBook
+from apps.examination.examquestion.serializers import ExamQuestionSimpleSerializer
+from apps.examination.exam.serializers import ExamAnswerLogSimpleSerializer
+
+
+class ErrorBookSerializer(serializers.ModelSerializer):
+    question = serializers.SerializerMethodField()
+    answer = serializers.SerializerMethodField()
+    exam_name = serializers.CharField(source='last_answer_log.main.exampaper.name', read_only=True)
+
+    class Meta:
+        model = ErrorBook
+        fields = ('last_time', 'wrong_count', 'exam_name', 'question', 'answer', )
+
+    def get_question(self, obj):
+        return ExamQuestionSimpleSerializer(obj.question).data
+
+    def get_answer(self, obj):
+        return ExamAnswerLogSimpleSerializer(obj.last_answer_log).data

+ 66 - 1
apps/practise/practiselog/models.py

@@ -1,10 +1,75 @@
 # coding=utf-8
 
 from django.db import models
+from django.utils import timezone
+from django.conf import settings
+from apps.foundation.models import Chapter, Subject
+from apps.examination.examquestion.models import ExamQuestion, ExamQuestionOption
 
 class PractiseLog(models.Model):
+    SUBJECT = 1
+    CHAPTER = 2
+    TYPE_CHOICES = (
+        (SUBJECT, u'科目'),
+        (CHAPTER, u'章节'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    subject = models.ForeignKey(Subject, verbose_name=u"科目", on_delete=models.PROTECT)
+    chapter = models.ForeignKey(Chapter, verbose_name=u"章节", on_delete=models.PROTECT, null=True,)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类型")
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'练习人', on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"开始时间", default=timezone.now, editable=False)
+    submit_time = models.DateTimeField(verbose_name=u"结束时间", null=True, blank=True)
+
+    begin_answer = models.ForeignKey('PractiseAnswerLog', verbose_name=u"开始习题", null=True, related_name='practise_log_begin_answer', editable=False, on_delete=models.PROTECT)
+    end_answer = models.ForeignKey('PractiseAnswerLog', verbose_name=u"结束习题", null=True, related_name='practise_log_end_answer', editable=False, on_delete=models.PROTECT)
+    right_count = models.IntegerField(verbose_name=u'正确数量', default=0, editable=False)
+    wrong_count = models.IntegerField(verbose_name=u'错误数量', default=0, editable=False)
+    total_count = models.IntegerField(verbose_name=u'总数量', default=0, editable=False)
+
     class Meta:
         db_table = "practise_log"
         ordering = ['-id']
         verbose_name = u"练习记录"
-        default_permissions = ()
+        default_permissions = ()
+
+class PractiseAnswerLog(models.Model):
+    RIGHT = 1
+    WRONG = 2
+    STATUS_CHOICES = (
+        (RIGHT, u'正确'),
+        (WRONG, u'错误'),
+    )
+    STATUS_JSON = [{'id': item[0], 'value': item[1]} for item in STATUS_CHOICES]
+
+    main = models.ForeignKey(PractiseLog, verbose_name=u"练习记录", on_delete=models.PROTECT)
+    question = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u'回答状态', null=True)
+
+    class Meta:
+        db_table = "practise_answer_log"
+        ordering = ['id']
+        verbose_name = u"练习答题记录"
+        default_permissions = ()
+
+class PractiseAnswerOptionLog(models.Model):
+    main = models.ForeignKey(PractiseAnswerLog, verbose_name=u"练习答题记录", on_delete=models.PROTECT)
+    option = models.ForeignKey(ExamQuestionOption, verbose_name=u"选项", on_delete=models.PROTECT)
+
+    class Meta:
+        db_table = "practise_answer_option_log"
+        ordering = ['id']
+        verbose_name = u"选择题回答"
+        default_permissions = ()
+
+class PractiseAnswerFillLog(models.Model):
+    main = models.ForeignKey(PractiseAnswerLog, verbose_name=u"练习答题记录", on_delete=models.PROTECT)
+    content = models.TextField(verbose_name=u"内容", blank=True)
+    order = models.IntegerField(verbose_name=u'排序')
+
+    class Meta:
+        db_table = "practise_answer_fill_log"
+        ordering = ['order', 'id']
+        verbose_name = u"填空题回答"
+        default_permissions = ()

+ 89 - 0
apps/practise/practiselog/serializers.py

@@ -0,0 +1,89 @@
+# coding=utf-8
+from rest_framework import serializers
+from .models import *
+
+
+class PractiseLogSerializer(serializers.ModelSerializer):
+    begin_answer = serializers.SerializerMethodField()
+    end_answer = serializers.SerializerMethodField()
+    next_practise = serializers.SerializerMethodField()
+
+    def get_next_practise(self, obj):
+        # 继续练习的下一题id
+        next_practise = ''
+        if obj.end_answer:
+            now_type = obj.end_answer.question.type
+            questions = ExamQuestion.objects.filter(type=now_type, delete=False).order_by('id').values_list('id', flat=True)
+            if obj.chapter:
+                questions = questions.filter(chapter=obj.chapter)
+            else:
+                questions = questions.filter(chapter__subject=obj.subject)
+            try:
+                # ValueError: 1 is not in list
+                now_question_index = list(questions).index(obj.end_answer.question.id)
+            except ValueError:
+                now_question_index = 0
+            if now_question_index < (len(questions) -1):
+                # 该题型未练习完,继续返回该题型下的习题
+                next_practise = questions[now_question_index + 1]
+            else:
+                # 该题型已练习完,返回下一个题型下的习题
+                while now_type <= ExamQuestion.JUDGMENT:
+                    now_type += 1
+                    questions = ExamQuestion.objects.filter(type=now_type, delete=False)
+                    if obj.chapter:
+                        questions = questions.filter(chapter=obj.chapter)
+                    else:
+                        questions = questions.filter(chapter__subject=obj.subject)
+                    questions = questions.order_by('id').first()
+                    if questions:
+                        next_practise = questions.id
+                        break
+        return next_practise
+
+    def get_end_answer(self, obj):
+        if obj.end_answer:
+            now_type = obj.end_answer.question.type
+            # todo 按id查
+            questions = ExamQuestion.objects.filter(type=now_type, delete=False).order_by('id').values_list('id', flat=True)
+            if obj.chapter:
+                questions = questions.filter(chapter=obj.chapter)
+            else:
+                questions = questions.filter(chapter__subject=obj.subject)
+            try:
+                # ValueError: 1 is not in list
+                now_question_index = list(questions).index(obj.end_answer.question.id)
+            except ValueError:
+                now_question_index = 0
+            name = '{0}/{1} {2}第{3}题'.format(obj.end_answer.question.chapter.name,
+                                         obj.end_answer.question.chapter.subject.name,
+                                         ExamQuestion.TYPE_CHOICES[obj.end_answer.question.type - 1][1],
+                                             now_question_index + 1
+                                         )
+            return name
+        return ''
+
+    def get_begin_answer(self, obj):
+        if obj.begin_answer:
+            now_type = obj.begin_answer.question.type
+            questions = ExamQuestion.objects.filter(type=now_type, delete=False).order_by('id').values_list('id', flat=True)
+            if obj.chapter:
+                questions = questions.filter(chapter=obj.chapter)
+            else:
+                questions = questions.filter(chapter__subject=obj.subject)
+            try:
+                # ValueError: 1 is not in list
+                now_question_index = list(questions).index(obj.begin_answer.question.id)
+            except ValueError:
+                now_question_index = 0
+            name = '{0}/{1} {2}第{3}题'.format(obj.begin_answer.question.chapter.name,
+                                         obj.begin_answer.question.chapter.subject.name,
+                                         ExamQuestion.TYPE_CHOICES[obj.begin_answer.question.type - 1][1],
+                                         now_question_index + 1
+                                         )
+            return name
+        return ''
+
+    class Meta:
+        model = PractiseLog
+        fields = '__all__'

+ 9 - 1
apps/staff/filters.py

@@ -2,6 +2,7 @@
 import django_filters
 
 from django.contrib.auth import get_user_model
+from .models import Department
 
 User = get_user_model()
 
@@ -11,4 +12,11 @@ class UserFilter(django_filters.FilterSet):
 
     class Meta:
         model = User
-        fields = ['username', 'is_active']
+        fields = ['username', 'is_active', 'type', ]
+
+class DepartmentFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(field_name='name', lookup_expr='icontains')
+
+    class Meta:
+        model = Department
+        fields = ['name',]

+ 4 - 2
apps/staff/models.py

@@ -63,9 +63,10 @@ class UserManager(BaseUserManager):
         return self.create_user(User.ADMINSTRATOR, username, password, **extra_fields)
 
     def create_staff(self, username, password=None, **extra_fields):
-        return self.create_user(User.STUDENT, username, password, **extra_fields)
+        return self.create_user(User.STAFF, username, password, **extra_fields)
 
     def create_superuser(self, username, password, **extra_fields):
+        extra_fields['name'] = username
         u = self.create_administrator(username, password, **extra_fields)
         u.is_active = True
         u.is_superuser = True
@@ -134,9 +135,10 @@ class User(AbstractBaseUser, PermissionsMixin):
     ADMINSTRATOR = 1
     STAFF = 2
 
-    type = models.PositiveSmallIntegerField(verbose_name=u"类型", editable=False)
+    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)
+    name = models.CharField(verbose_name=u'姓名', max_length=30)
     is_active = models.BooleanField(verbose_name=u'激活', default=True)
     date_joined = models.DateTimeField(verbose_name=u'注册时间', default=timezone.now, editable=False)
 

+ 174 - 0
apps/staff/serializers.py

@@ -0,0 +1,174 @@
+# coding=utf-8
+from django.contrib.auth import get_user_model, authenticate
+from django.db.models import F
+
+from rest_framework import serializers
+from rest_framework_jwt.serializers import JSONWebTokenSerializer
+from rest_framework_jwt.settings import api_settings
+
+from utils import get_remote_addr
+from utils.exceptions import CustomError
+
+from apps.system.models import SysLog
+from .models import Department
+
+User = get_user_model()
+jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
+jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
+
+class AdminUserJWTSerializer(JSONWebTokenSerializer):
+    def validate(self, attrs):
+        credentials = {
+            self.username_field: attrs.get(self.username_field),
+            'password': attrs.get('password')
+        }
+
+        if all(credentials.values()):
+            user = authenticate(**credentials)
+
+            if user:
+                if not user.is_active:
+                    msg = u'禁用帐户,禁止登录'
+                    SysLog.objects.addnew(user, SysLog.INSERT,u'禁用帐户[%s]尝试登录管理系统,IP[%s]' % (user.username, get_remote_addr(self.request)))
+                    raise serializers.ValidationError(msg)
+
+                if not user.is_administrator():
+                    msg = u'非管理员账号,禁止登录'
+                    SysLog.objects.addnew(user, SysLog.INSERT,u'非管理员账号[%s]尝试登录管理系统,IP[%s]' % (user.username, get_remote_addr(self.request)))
+                    raise serializers.ValidationError(msg)
+
+                payload = jwt_payload_handler(user)
+                SysLog.objects.addnew(user, SysLog.INSERT, u'[%s]登录管理系统,IP[%s]' % (user.username,get_remote_addr(self.request)))
+
+                return {
+                    'token': jwt_encode_handler(payload),
+                    'user_id': user.id,
+                    'username': user.username,
+                    'name': user.name
+                }
+            else:
+                msg = u'账号或者密码错误!'
+                SysLog.objects.addnew(None, SysLog.INSERT, u'登录管理系统失败[%s][%s],IP[%s]' % (attrs[self.username_field], attrs['password'], get_remote_addr(self.request)))
+                raise serializers.ValidationError(msg)
+        else:
+            msg = u'必须包含“{username field}”和“password'
+            msg = msg.format(username_field=self.username_field)
+            raise serializers.ValidationError(msg)
+
+class StaffUserJWTSerializer(JSONWebTokenSerializer):
+    def validate(self, attrs):
+        credentials = {
+            self.username_field: attrs.get(self.username_field),
+            'password': attrs.get('password')
+        }
+
+        if all(credentials.values()):
+            user = authenticate(**credentials)
+
+            if user:
+                if not user.is_active:
+                    msg = u'禁用帐户,禁止登录'
+                    SysLog.objects.addnew(user, SysLog.INSERT,u'禁用帐户[%s]尝试登录答题系统,IP[%s]' % (user.username, get_remote_addr(self.request)))
+                    raise serializers.ValidationError(msg)
+
+                if not user.is_staff():
+                    msg = u'非工作账号,禁止登录'
+                    SysLog.objects.addnew(user, SysLog.INSERT,u'非工作账号[%s]尝试登录答题系统,IP[%s]' % (user.username, get_remote_addr(self.request)))
+                    raise serializers.ValidationError(msg)
+
+                payload = jwt_payload_handler(user)
+                SysLog.objects.addnew(user, SysLog.INSERT, u'[%s]登录答题系统,IP[%s]' % (user.username,get_remote_addr(self.request)))
+
+                return {
+                    'token': jwt_encode_handler(payload),
+                    'user_id': user.id,
+                    'username': user.username,
+                    'name': user.name
+                }
+            else:
+                msg = u'账号或者密码错误!'
+                SysLog.objects.addnew(None, SysLog.INSERT, u'登录答题系统失败[%s][%s],IP[%s]' % (attrs[self.username_field], attrs['password'], get_remote_addr(self.request)))
+                raise serializers.ValidationError(msg)
+        else:
+            msg = u'必须包含“{username field}”和“password'
+            msg = msg.format(username_field=self.username_field)
+            raise serializers.ValidationError(msg)
+
+
+class DepartmentSerializer(serializers.ModelSerializer):
+    lft = serializers.IntegerField(read_only=True)
+    rgt = serializers.IntegerField(read_only=True)
+    out_parent_id = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Department
+        fields = '__all__'
+
+    def get_out_parent_id(self, obj):
+        if obj.parent_id:
+            return obj.parent_id
+        return 0
+
+    def create(self, validated_data):
+        count = Department.objects.filter(name=validated_data['name']).count()
+        if count:
+            raise CustomError(u'名称为[%s]的部门已存在!' % validated_data['name'])
+        parent = None
+        if 'parent_id' in validated_data:
+            parent = Department.getById(validated_data['parent_id'])
+            validated_data['parent_id'] = parent.id
+
+        lft = Department.getLft(parent)
+        Department.objects.filter(rgt__gt=lft).update(rgt=F('rgt') + 2)
+        Department.objects.filter(lft__gt=lft).update(lft=F('lft') + 2)
+
+        validated_data['lft'] = lft + 1
+        validated_data['rgt'] = lft + 2
+
+        instance = super(DepartmentSerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        print(validated_data)
+        name = validated_data['name']
+        count = Department.objects.filter(name=name).exclude(id=instance.id).count()
+        if count:
+            raise CustomError(u'名称为[%s]的部门已存在!' % name)
+        instance = super(DepartmentSerializer, self).update(instance, validated_data)
+        return instance
+
+
+class UserSerializer(serializers.ModelSerializer):
+    password = serializers.CharField(write_only=True, allow_blank=True)
+    status_text = serializers.SerializerMethodField()
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    type_text = serializers.SerializerMethodField()
+
+    class Meta:
+        model = User
+        fields = '__all__'
+
+    def get_status_text(self, obj):
+        if obj.is_active:
+            return u'是'
+        return u'否'
+
+    def get_type_text(self, obj):
+        if obj.type == User.ADMINSTRATOR:
+            return u'管理员'
+        elif obj.type == User.STAFF:
+            return u'员工'
+        return ''
+
+    def create(self, validated_data):
+        if validated_data['password'].strip() == '':
+            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:
+            user = User.objects.create_administrator(validated_data['username'], validated_data['password'], name=validated_data['name'], is_active=validated_data['is_active'], department=validated_data['department'])
+        return user
+
+    def update(self, instance, validated_data):
+        instance.update_item(validated_data)
+        return instance

+ 3 - 1
apps/system/filters.py

@@ -9,10 +9,12 @@ from utils.format import clean_datetime_range
 class SysLogFilter(django_filters.FilterSet):
     type = django_filters.ChoiceFilter(choices=SysLog.TYPE_CHOICES, field_name='type')
     create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+    name = django_filters.CharFilter(field_name="user__name", lookup_expr="icontains")
+    description = django_filters.CharFilter(field_name="description", lookup_expr="icontains")
 
     class Meta:
         model = SysLog
-        fields = ('create_time', 'type', )
+        fields = ('create_time', 'type', 'name', 'description', )
 
     def __init__(self, data=None, *args, **kwargs):
         data = clean_datetime_range(data, 'create_time')

+ 16 - 0
apps/system/serializers.py

@@ -0,0 +1,16 @@
+# coding=utf-8
+
+from rest_framework import serializers
+
+from .models import SysLog
+
+
+class SysLogSerializer(serializers.ModelSerializer):
+    user_name = serializers.CharField(source='user.name', read_only=True)
+    username = serializers.CharField(source='user.username', read_only=True)
+    user_department = serializers.CharField(source='user.department.name', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+
+    class Meta:
+        model = SysLog
+        fields = '__all__'

+ 1 - 1
ks/settings.py

@@ -211,4 +211,4 @@ REST_FRAMEWORK = {
 try:
     from ks.local_settings import *
 except ImportError:
-    pass
+    pass

+ 1 - 1
ks/urls.py

@@ -26,4 +26,4 @@ urlpatterns = [
 ]
 
 urlpatterns += static(settings.UIS_URL, document_root=settings.UIS_ROOT)
-urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

+ 21 - 0
uis/admin/dashboard/home.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html style="height: 100%;">
+<head>
+  <meta charset="utf-8">
+  <title>首页</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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">
+</head>
+<body style="height: 100%;">
+
+<div class="layui-card" style="height: 100%;display:flex;">
+  <div style="margin:auto;font-weight:bold;font-size: 32px;font-style: normal;color:#868686;">
+    欢迎使用人员能力评估后台管理系统
+  </div>
+</div>
+
+</body>
+</html>

+ 95 - 0
uis/admin/department/edit.html

@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>部门信息</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+        <div class="layui-row layui-col-space15">
+          <div class="layui-col-md12">
+             <form class="layui-form" action="" lay-filter="component-form-element">
+                  <div class="layui-row layui-col-space10 layui-form-item">
+
+                      <div class="layui-col-md12">
+                          <label class="layui-form-label"><font color='red' size="4">*</font>名称:</label>
+                          <div class="layui-input-block">
+                              <input type="text" name="name" lay-verify="required" autocomplete="off" class="layui-input">
+                          </div>
+                      </div>
+                      <div class="layui-col-md12">
+                          <label class="layui-form-label">备注:</label>
+                          <div class="layui-input-block">
+                              <input type="text" name="notes" placeholder="" autocomplete="off" class="layui-input">
+                          </div>
+                      </div>
+                      <button id="submit_btn" style="display: none;" class="layui-btn" lay-submit lay-filter="component-form-element"></button>
+
+                  </div>
+              </form>
+          </div>
+        </div>
+        </div>
+    </div>
+</div>
+
+
+  <script src="../../layuiadmin/layui/layui.js"></script>
+  <script>
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index' //主入口模块
+  }).use(['index', 'form'], function(){
+    var $ = layui.$
+    ,admin = layui.admin
+    ,element = layui.element
+    ,form = layui.form
+    var id = layui.view.getParameterByName('id');
+    var parent_id = layui.view.getParameterByName('parent_id');
+
+    form.render(null, 'component-form-element');
+    element.render('breadcrumb', 'breadcrumb');
+
+    if (id){
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        form.val("component-form-element", editdata);
+        var url = '/admin/department/' + id + '/';
+        var type = 'put';
+    }else{
+      url = '/admin/department/';
+      type = 'post';
+    }
+
+    form.on('submit(component-form-element)', function(data){
+        var submitData = data.field;
+
+        if (parent_id && parent_id !== '0'){
+            submitData['parent_id'] = parent_id
+        }
+      admin.req({
+        url: url
+        ,data: submitData
+        ,type: type
+        ,done: function(res){
+            parent.layui.onSubmitChild(res.data);
+        }
+      });
+
+      return false;
+    });
+    parent.layui.submitChild = function () {
+      $("#submit_btn").click();
+    };
+  });
+  </script>
+</body>
+</html>

+ 183 - 0
uis/admin/department/index.html

@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>部门管理</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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">
+</head>
+<style type="text/css">
+    .LAY-btns .layui-nav {padding-left:0;padding-right:10px;top:-4px;border: 0;background-color: #009688;}
+    .LAY-btns .layui-nav .layui-nav-item{line-height: 30px;}
+    .LAY-btns .layui-nav .layui-nav-child{top:34px;}
+    .LAY-btns .layui-nav .layui-nav-bar{display: none;}
+    .LAY-btns .layui-nav .layui-nav-child dd.layui-this a{color:#333;background-color:#fff;}
+    .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {background-color: #f2f2f2;color: #000;}
+  </style>
+<body>
+
+  <div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+        <div class="layui-row layui-col-space15">
+          <div class="layui-col-md12">
+
+            <div class="LAY-btns" style="margin-bottom: 10px;">
+              <button class="layui-nav">
+                <div class="layui-nav-item">
+                  <a href="javascript:;" style="color:#fff;" >添加</a>
+                  <dl class="layui-nav-child"> <!-- 二级菜单 -->
+                    <dd><a href="#" id="btn_add">部门</a></dd>
+                    <dd><a href="#" id="btn_add_child">子部门</a></dd>
+                  </dl>
+                </div>
+              </button>
+            </div>
+
+            <table  id="datagrid" lay-filter="datagrid-operate"></table>
+            <script type="text/html" id="datagrid-operate-bar">
+              <div class="layui-btn-group">
+                <a class="layui-btn layui-btn-xs" lay-event="edit">修改</a>
+                <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
+              </div>
+            </script>
+          </div>
+        </div>
+        </div>
+    </div>
+  </div>
+
+  <script src="../../layuiadmin/layui/layui.js?t=1"></script>
+  <script>
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index' //主入口模块
+  }).use(['index', 'table', 'treetable', 'utils'], function(){
+    var $ = layui.$;
+    var layer  = layui.layer ;
+    var table  = layui.table ;
+    var utils  = layui.utils ;
+    var treetable   = layui.treetable  ;
+
+    var renderTable = function () {
+        layer.load(2);
+        treetable.render({
+            treeColIndex: 1,
+            treeSpid: "0",
+            treeIdName: 'id',
+            treePidName: 'out_parent_id',
+            treeDefaultClose: false,
+            treeLinkage: false,
+            elem: '#datagrid',
+            url: '/admin/department/',
+            page: false,
+            cols: [[
+                {type:'checkbox',width:50},
+                {field: 'name', title: '名称',width:170},
+                {field: 'notes', title: '备注', minWidth:150},
+                {width:100,align:'left', fixed: 'right',templet: '#datagrid-operate-bar'}
+            ]],
+            done: function () {
+               //layui.index.removeNoPermButtons();
+                layer.closeAll('loading');
+            }
+        });
+    };
+
+
+    renderTable();
+
+    //监听工具条
+    table.on('tool(datagrid-operate)', function(obj){
+      var data = obj.data;
+        if(obj.event === 'edit'){
+          table.editdata = data;
+
+          layer.open({
+            type: 2,
+            title: '修改',
+            shadeClose: false,
+            area: ['500px', '300px'],
+            btn: ['保存', '取消'],
+            yes: function(index, dom){
+               layui.onSubmitChild = function (data) {
+              layer.close(index);
+              renderTable();
+            };
+            layui.submitChild();
+            },
+            btn2: function(index, layero){
+              layer.close(index);//关闭当前按钮
+            },
+            content: 'edit.html?id='+data.id + '&parent_id=' + data.out_parent_id
+          });
+        }else if(obj.event === 'del'){
+          layer.confirm('确定要删除吗?', function(index){
+              layer.close(index);
+            layui.admin.req({
+              url: '/admin/department/'+ data.id + '/'
+                ,type: 'delete'
+              ,done: function(res){
+                renderTable();
+              }
+            });
+          });
+        }
+    });
+
+    $('#btn_add').on('click', function(){
+        layer.open({
+          type: 2,
+          title: '添加',
+          shadeClose: false,
+          area: ['500px', '300px'],
+          btn: ['保存', '取消'],
+          yes: function(index, dom){
+             layui.onSubmitChild = function (data) {
+              layer.close(index);
+              renderTable();
+            };
+            layui.submitChild();
+          },
+          btn2: function(index, layero){
+            layer.close(index);//关闭当前按钮
+          },
+          content: 'edit.html'
+        });
+    });
+
+    $('#btn_add_child').on('click', function(){
+        var data = utils.getSelectedRow('datagrid');
+         if(data == false){
+             return;
+         }
+         var parent_id = data.id;
+
+        layer.open({
+          type: 2,
+          title: '添加',
+          shadeClose: false,
+          area: ['500px', '270px'],
+          btn: ['保存', '取消'],
+          yes: function(index, dom){
+              layui.onSubmitChild = function (data) {
+              layer.close(index);
+              renderTable();
+            };
+            layui.submitChild();
+          },
+          btn2: function(index, layero){
+            layer.close(index);//关闭当前按钮
+          },
+          content: 'edit.html?parent_id='+parent_id
+        });
+    });
+  });
+  </script>
+</body>
+</html>
+

+ 98 - 0
uis/admin/exam/details.html

@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试题管理详情</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+    <style type="text/css">
+        .title {
+            width: 140px;
+            background: #efefef;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+
+                    <div id="print_div">
+                        <table class="layui-table">
+
+                            <tr>
+                                <td class="title">名称:</td>
+                                <td id="name" class="cell"></td>
+                                <td class="title">科目</td>
+                                <td id="subject_name" class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">时长:</td>
+                                <td id="duration" class="cell"></td>
+                                <td class="title">考试时间:</td>
+                                <td id="exam_time" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">总分:</td>
+                                <td id="question_total_scores" class="cell"></td>
+                                <td class="title">及格线:</td>
+                                <td id="passline" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">试卷类型:</td>
+                                <td id="type_text" class="cell"></td>
+                                <td class="title">试卷:</td>
+                                <td id="exampaper_name" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">添加人:</td>
+                                <td id="create_user_text" class="cell"></td>
+                                <td class="title">添加时间:</td>
+                                <td id="create_time" class="cell"></td>
+                            </tr>
+                            <tr id="id_analysis">
+                                <td class="title">备注:</td>
+                                <td colspan="3" id="desc" class="cell"></td>
+                            </tr>
+                            <tr id="id_analysis">
+                                <td class="title">参加人员:</td>
+                                <td colspan="3" id="examinee_name" class="cell"></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js?t=1"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table'], function () {
+        var $ = layui.$;
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        $('.cell').each(function (index, element) {
+            if (element.id === 'passline'){
+                element.innerHTML += (editdata[element.id] || 0)
+            }else{
+                element.innerHTML += (editdata[element.id] || '')
+            }
+
+        });
+
+    });
+</script>
+</body>
+</html>

+ 108 - 0
uis/admin/exam/edit.html

@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>添加运维宝典</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>名称:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="name" class="layui-input" lay-verify="required">
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>考试时间:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="exam_time" class="layui-input" id="id_exam_time" lay-verify="required">
+                        </div>
+                    </div>
+
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>时长:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="duration" lay-verify="intGeZ|required" class="layui-input">
+                        </div>
+                    </div>
+
+
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">备注:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" name="desc"></textarea>
+                    </div>
+                </div>
+
+                <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index',
+    }).use(['index', 'form', 'utils', 'laydate', ], function () {
+        var $ = layui.$
+            , admin = layui.admin
+            ,laydate = layui.laydate
+            , form = layui.form;
+
+        var id = layui.view.getParameterByName('id');
+
+        form.render(null, 'component-form-element');
+        laydate.render({
+            elem: '#id_exam_time'
+            ,type: 'datetime'
+            ,trigger: 'click'
+            ,format: 'yyyy-MM-dd HH:mm:ss'
+        })
+
+        if (id) {
+            var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+            form.val("component-form-element", editdata);
+        }
+
+        var url = id ? '/admin/exam/' + id + "/" : '/admin/exam/';
+        var method = id ? 'put' : 'post';
+
+        form.on('submit(component-form-element)', function(data){
+
+          admin.req({
+            url: url
+            , data: data.field
+            , type: method
+            ,done: function(res){
+                parent.layui.onSubmitChild(res.data);
+            }
+          });
+
+          return false;
+        });
+
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+    });
+</script>
+</body>
+</html>

+ 213 - 0
uis/admin/exam/examinee_edit.html

@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>科目章节设置</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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">
+    <style type="text/css">
+        .seach_items {float:right;margin-left: 10px;}
+    </style>
+
+</head>
+<body>
+
+  <div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+        <div class="layui-row layui-col-space15">
+          <div class="layui-col-md4">
+            <div class="LAY-btns" style="margin-bottom: 5px;">
+                    <div class="seach_items">
+                        <button class="layui-btn" id="search_user">查询</button>
+                    </div>
+                    <div class="seach_items">
+                        <input type="text"  id="id_name" autocomplete="off" class="layui-input" placeholder="名称"/>
+                    </div>
+                <div style="clear: both;"></div>
+            </div>
+            <table class="layui-hide" id="subject_datagrid"></table>
+              <button class="layui-btn" id="add_check" style="margin-top: 5px">添加选中</button>
+          </div>
+
+          <div class="layui-col-md4">
+            <div class="LAY-btns" style="margin-bottom: 10px;">
+                  <div class="layui-col-xs2">
+                        <label class="layui-form-label">选中的人员</label>
+                    </div>
+                    <div style="clear: both;"></div>
+                </div>
+
+            <table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
+          <button class="layui-btn" id="remove_check" style="margin-top: 10px">删除选中</button>
+          </div>
+
+            <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+        </div>
+        </div>
+    </div>
+  </div>
+  <script src="../../layuiadmin/layui/layui.js?t=1"></script>
+  <script>
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index' //主入口模块
+  }).use(['index', 'table', 'form'], function(){
+    var $ = layui.$;
+    var table = layui.table
+        ,form = layui.form
+        ,admin = layui.admin;
+    var exam_id = layui.view.getParameterByName('exam_id');
+    if (!exam_id){
+        layer.msg('请选择考试!', {icon: 2});
+        return false
+    }
+
+    admin.req({
+        url: '/admin/exam/' + exam_id + '/examinee/',
+        type:'get',
+        done: function (res) {
+            tableIns.reload({
+                data: res.data,
+                cols: [cols],
+            });
+        }
+    });
+
+
+    table.render({
+      elem: '#subject_datagrid'
+      ,url: '/admin/user/?type=2'
+      ,cols: [[
+          {type:'checkbox'}
+        ,{field:'name', title:'姓名'}
+        ,{field:'username', title:'帐号'}
+        ,{field:'department_text', title:'部门'}
+      ]]
+        ,height: 'full-145'
+      ,page: true
+      , done: function () {
+      }
+    });
+
+
+    $('#search_user').on('click', function () {
+        table.reload('subject_datagrid', {
+            where: {username: $('#id_name').val()}
+            , page: {curr: 1}
+        });
+    })
+
+    var cols = [
+        {type:'checkbox'},
+        {field: 'name', title: '姓名'},
+        {field: 'username', title: '帐号'},
+        {field: 'department_text', title: '部门'},
+    ]
+
+    var layTableId = "layTable";
+    var tableIns = table.render({
+        elem: '#dataTable',
+        id: layTableId,
+        data: [],
+        page: false,
+        limit:1000000,
+        loading: true,
+        height: 'full-145',
+        even: true, //不开启隔行背景
+        cols: [cols],
+        done: function (data, date, total) {
+        }
+    });
+
+
+
+
+     $('#add_check').on('click', function () {
+          var selectData = layui.table.checkStatus('subject_datagrid').data;
+          // 获取左边表格选中数据后 刷新右边表格
+         var oldData = [];
+         oldData = table.cache[layTableId];
+
+         for (var i=0; i<selectData.length; i++){
+             var is_have = false;
+             for (var j=0; j<oldData.length; j++){
+                 if (selectData[i].id === oldData[j].id){
+                     is_have = true
+                 }
+             }
+
+             if(!is_have){
+                 oldData.push({
+                     id: selectData[i].id,
+                     name: selectData[i].name,
+                     username: selectData[i].username,
+                     department_text: selectData[i].department_text
+                 })
+             }
+         }
+
+         tableIns.reload({
+            data: oldData,
+            cols: [cols],
+        });
+      })
+
+      $('#remove_check').on('click', function () {
+          var oldData = table.cache[layTableId];
+          var newData = []
+
+          for (var i=0; i<oldData.length; i++){
+              if (!oldData[i].LAY_CHECKED){
+                  newData.push(oldData[i])
+              }
+          }
+
+          tableIns.reload({
+            data: newData,
+            cols: [cols],
+        });
+      })
+
+
+      form.on('submit(component-form-element)', function(data){
+          var users = table.cache[layTableId];
+          if (users.length === 0){
+              layer.msg('请添加考生!', {icon: 2});
+                return false
+          }
+          if (!exam_id){
+            layer.msg('请选择考试!', {icon: 2});
+            return false
+        }
+          user_ids = [];
+          for (var i=0; i<users.length; i++){
+              user_ids.push(users[i].id)
+          }
+            admin.req({
+            url: '/admin/exam/' + exam_id + '/add_examinee/'
+            , data: {user_ids: JSON.stringify(user_ids)}
+            , type: 'post'
+            ,done: function(res){
+                parent.layui.onSubmitChild(res.data);
+            }
+          });
+
+          return false;
+        });
+
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+
+
+});
+  </script>
+</body>
+</html>

+ 420 - 0
uis/admin/exam/exampaper_edit.html

@@ -0,0 +1,420 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>添加运维宝典</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>科目:</label>
+                        <div class="layui-input-inline">
+                            <select name="subject" id="id_subject" lay-verify="required" lay-filter="subjectChange">
+                                <option value="">请选择科目</option>
+                            </select>
+                        </div>
+                    </div>
+
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>试卷类型:</label>
+                        <div class="layui-input-inline">
+                            <select name="type" lay-verify="required" lay-filter="typeChange" id="id_type">
+                                <option value="1" selected>固定试卷</option>
+                                <option value="2">随机试卷</option>
+                            </select>
+                        </div>
+                    </div>
+
+                </div>
+
+
+                <div class="layui-form-item" id="fixed_exampaper">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>选择试卷:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" id="exampaper_name" class="layui-input">
+                        </div>
+                        <div class="layui-input-inline">
+                            <button type="button" class="layui-btn" id="search_btn">查询 </button>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="layui-form-item">
+                    <div id="fixed">
+                        <label class="layui-form-label"></label>
+                        <div class="layui-input-block" id="id_table">
+                            <div id="tableRes" class="table-overlay" style="align-items: center">
+                                <table id="dataTable" lay-filter="dataTable"></table>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div id="random">
+                        <div class="layui-form-item">
+                            <label class="layui-form-label"><font color='red' size="4">*</font>配置:</label>
+                                <div class="layui-inline" style="width: 80%">
+                                <label class="layui-form-label">单选题:</label>
+                                <div class="layui-inline" style="width: 85%">
+                                    每题
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="single_scores" autocomplete="off" class="layui-input">
+                                      </div>
+                                    分
+                                    ,简单
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="single_simple_count" autocomplete="off" class="layui-input single">
+                                      </div>
+
+                                    ,中等
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="single_mid_count" autocomplete="off" class="layui-input single">
+                                      </div>
+                                    ,困难
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="single_hard_count" autocomplete="off" class="layui-input single">
+                                      </div>
+                                    <div class="layui-inline" style="width: 10%">
+                                        <label id="id_single_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                                      </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="layui-form-item">
+                            <label class="layui-form-label"></label>
+                                <div class="layui-inline" style="width: 80%">
+                                <label class="layui-form-label">多选题:</label>
+                                <div class="layui-inline" style="width: 85%">
+                                    每题
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="multiple_scores" autocomplete="off" class="layui-input">
+                                      </div>
+                                    分
+                                    ,简单
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="multiple_simple_count" autocomplete="off" class="layui-input multiple">
+                                      </div>
+
+                                    ,中等
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="multiple_mid_count" autocomplete="off" class="layui-input multiple">
+                                      </div>
+                                    ,困难
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="multiple_hard_count" autocomplete="off" class="layui-input multiple">
+                                      </div>
+                                    <div class="layui-inline" style="width: 10%">
+                                        <label id="id_multiple_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                                      </div>
+                                </div>
+                            </div>
+                        </div>
+
+                        <div class="layui-form-item">
+                            <label class="layui-form-label"></label>
+                                <div class="layui-inline" style="width: 80%">
+                                <label class="layui-form-label">判断题:</label>
+                                <div class="layui-inline" style="width: 85%">
+                                    每题
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="judgment_scores" autocomplete="off" class="layui-input">
+                                      </div>
+                                    分
+                                    ,简单
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="judgment_simple_count" autocomplete="off" class="layui-input judgment">
+                                      </div>
+
+                                    ,中等
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="judgment_mid_count" autocomplete="off" class="layui-input judgment">
+                                      </div>
+                                    ,困难
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="judgment_hard_count" autocomplete="off" class="layui-input judgment">
+                                      </div>
+                                    <div class="layui-inline" style="width: 10%">
+                                        <label id="id_judgment_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                                      </div>
+                                </div>
+                            </div>
+                        </div>
+
+                        <div class="layui-form-item">
+                            <label class="layui-form-label"></label>
+                                <div class="layui-inline" style="width: 80%">
+                                <label class="layui-form-label">填空题:</label>
+                                <div class="layui-inline" style="width: 85%">
+                                    每题
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="fill_scores" autocomplete="off" class="layui-input">
+                                      </div>
+                                    分
+                                    ,简单
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="fill_simple_count" autocomplete="off" class="layui-input fill">
+                                      </div>
+
+                                    ,中等
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="fill_mid_count" autocomplete="off" class="layui-input fill">
+                                      </div>
+                                    ,困难
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="fill_hard_count" autocomplete="off" class="layui-input fill">
+                                      </div>
+                                    <div class="layui-inline" style="width: 10%">
+                                        <label id="id_fill_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                                      </div>
+                                </div>
+                            </div>
+                        </div>
+
+                    </div>
+
+                </div>
+
+                <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index',
+    }).use(['index', 'form', 'utils', 'table', ], function () {
+        var $ = layui.$
+            , admin = layui.admin
+            , table = layui.table
+            , form = layui.form;
+
+        var exam_id = layui.view.getParameterByName('exam_id');
+
+
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                form.render();
+                loadData()
+            }
+        });
+        var loadData = function () {
+            var subject = $('#id_subject').val();
+            var type = $('#id_type').val();
+            if (type === '1'){
+                $('#fixed').show();
+                $('#fixed_exampaper').show();
+                $('#random').hide();
+
+                if (subject){
+                    table_reload(subject)
+                }
+
+            }else{
+                $('#fixed').hide();
+                $('#fixed_exampaper').hide();
+                $('#random').show();
+            }
+        };
+
+        $('.single').on("input", function (e) {
+            var total = 0;
+            $('.single').each(function (i) {
+                var target =  $('.single')[i];
+                var val = parseInt(target.value);
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+             var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_single_total_count').html(txt);
+        });
+        $('.multiple').on("input", function (e) {
+            var total = 0;
+            $('.multiple').each(function (i) {
+                var target =  $('.multiple')[i];
+                var val = parseInt(target.value);
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+            var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_multiple_total_count').html(txt);
+        });
+        $('.fill').on("input", function (e) {
+            var total = 0;
+            $('.fill').each(function (i) {
+                var target =  $('.fill')[i];
+                var val = parseInt(target.value);
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+            var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_fill_total_count').html(txt);
+        });
+        $('.judgment').on("input", function (e) {
+            var total = 0;
+            $('.judgment').each(function (i) {
+                var target =  $('.judgment')[i];
+                var val = parseInt(target.value);
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+            var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_judgment_total_count').html(txt);
+        });
+
+        form.render(null, 'component-form-element');
+
+        $('#search_btn').on('click', function () {
+            var subject = $('#id_subject').val();
+            var name = $('#exampaper_name').val();
+            table_reload(subject, name)
+        });
+
+        var typeChange = function (value) {
+            // 如果试卷类型是固定试卷  根据科目和类型 去后端请求相应类型的试卷 加载  如果是 随机试卷 改变table
+            if (value === '2'){
+                $('#fixed').hide();
+                $('#fixed_exampaper').hide();
+                $('#random').show();
+            }else if (value === '1'){
+                $('#fixed').show();
+                $('#fixed_exampaper').show();
+                $('#random').hide();
+            }else{
+                layer.msg('请选择一个合法试卷类型!', {icon: 2});
+                return false
+            }
+
+            var subject = $('#id_subject').val();
+            if (subject && value === '1'){
+                table_reload(subject);
+            }
+        };
+
+        var subjectChange = function (value) {
+            // 如果试卷类型是 固定试卷  科目改变了添加相应科目的固定试卷  如果是随机试卷不做处理
+            var type = $('#id_type').val();
+            if (type && type === '1'){
+                table_reload(value);
+            }
+        };
+        // 试卷类型修改 重新加载table
+        form.on('select(typeChange)', function (data) {
+            if (!data.value) return;
+            typeChange(data.value)
+        });
+        // 科目改变 重新加载table
+        form.on('select(subjectChange)', function (data) {
+            if (!data.value) return;
+            subjectChange(data.value)
+        });
+
+        var tbWidth = $('#tableRes').width();
+        var cols = [
+            {title: '', type: 'radio', width: '5%', },
+            {title: '编号', type: 'numbers', width: '8%', },
+            {title: '名称', field: 'name', width: '20%', },
+            {title: '科目', field: 'subject_name', width: '15%', },
+            {title: '总分', field: 'question_total_scores', width: '10%', },
+            {title: '及格线', field: 'passline', width: '10%', },
+            {title: '添加人', field: 'create_user_text', width: '10%', },
+            {title: '时间', field: 'create_time', width: '22%', },
+        ]
+        
+        var layTableId = 'layTable';
+        var tableIns = table.render({
+            elem: '#dataTable',
+            id: layTableId,
+            data: [],
+            width: tbWidth,
+            page: true,
+            limit: 10,
+            loading: true,
+            even: true,
+            cols: [cols],
+            done: function (data, date, total) {
+            }
+        })
+
+        var table_reload = function (subject, name='') {
+            admin.req({
+                url: '/admin/exampaper/?subject=' + subject + '&name=' + name + '&type=2',
+                type: 'get',
+                done: function (res) {
+                    tableIns.reload({
+                        data: res.data,
+                        cols: [cols],
+                    });
+                }
+            });
+        };
+
+        form.on('submit(component-form-element)', function(data){
+            if (data.field.type === '1'){
+                var url = '/admin/exam/' + exam_id + '/allocation_fixed_exampaper/';
+                var tableData = table.cache[layTableId], exampaper_id = '', checked = 0;
+
+                for (var i = 0; i < tableData.length; i++) {
+                    if (tableData[i].LAY_CHECKED){
+                        checked += 1;
+                        exampaper_id = tableData[i].id;
+                    }
+                }
+
+                if (checked !==1 ){
+                    layer.msg('请选择一张试卷!', {icon: 2});
+                    return false;
+                }
+                data.field['exampaper_id'] = exampaper_id
+            }else{
+                url = '/admin/exam/' + exam_id + '/allocation_random_exampaper/';
+            }
+
+            admin.req({
+            url: url
+            , data: data.field
+            , type: 'post'
+            ,done: function(res){
+                parent.layui.onSubmitChild(res.data);
+            }
+          });
+
+          return false;
+        });
+
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+    });
+</script>
+</body>
+</html>

+ 298 - 0
uis/admin/exam/index.html

@@ -0,0 +1,298 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>考试管理</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=agent-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">
+    <style type="text/css">
+        .seach_items {
+            float: right;
+            margin-left: 10px;
+        }
+    </style>
+    <style type="text/css">
+        .LAY-btns .layui-nav {
+            padding-left: 0;
+            padding-right: 10px;
+            top: -4px;
+            margin: 0 10px;
+            border: 0;
+            background-color: #009688;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-item {
+            line-height: 40px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child {
+            top: 34px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-bar {
+            display: none;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a {
+            color: #333;
+            background-color: #fff;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {
+            background-color: #f2f2f2;
+            color: #000;
+        }
+
+        .tableContent {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            flex-direction: row;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <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 layui-btn-sm" id="exam_add">
+                                <i class="layui-icon layui-icon-add-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
+                                        class="layui-icon layui-icon-search"></i>查询
+                                </button>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="name" autocomplete="off" class="layui-input" placeholder="名称关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <select name="type">
+                                    <option value="" selected>请选择试卷类型</option>
+                                    <option value="1">固定试卷</option>
+                                    <option value="2">随机试卷</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_subject" name="subject">
+                                    <option value="" selected>请选择科目</option>
+                                </select>
+                            </div>
+
+                        </form>
+                        <div style="clear: both;"></div>
+                    </div>
+                    <table class="layui-hide" id="exam_datagrid" lay-filter="exam_question-operate"></table>
+
+                    <script type="text/html" id="exam_question-operate-bar">
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exampaper_distribution"
+                            >分配试卷</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="examinee_add"
+                            >添加考生</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exam_detail"
+                            >查看</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exam_edit"
+                            >修改</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exam_delete"
+                            >删除</a>
+                        </div>
+                    </script>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'form', 'admin',], function () {
+        var $ = layui.$;
+        var table = layui.table
+            , admin = layui.admin
+            , form = layui.form;
+
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                 form.render();
+            }
+        });
+
+        table.render({
+            elem: '#exam_datagrid'
+            , url: '/admin/exam/'
+            , cols: [[
+                {title: '编号', type: 'numbers'}
+                , {field: 'name', title: '名称', width: 200}
+                , {field: 'subject_name', title: '科目', width: 120}
+                , {field: 'exam_time', title: '考试时间', width: 180}
+                , {field: 'type_text', title: '试卷类型', width: 120}
+                , {field: 'examinee_count', title: '考试人数', width: 120}
+                , {field: 'duration', title: '时长', width: 120}
+                , {field: 'question_total_scores', title: '总分', width: 80}
+                , {field: 'passline', title: '及格线', width: 80}
+                , {field: 'create_user_text', title: '添加人', width: 100}
+                , {field: 'create_time', title: '添加时间', width: 180}
+                , {title: '操作', width: 310, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
+            ]]
+            , page: true
+            , height: 'full-108'
+        });
+
+        //监听工具条
+        table.on('tool(exam_question-operate)', function (obj) {
+            var data = obj.data;
+            table.editdata = data;
+            if (obj.event === 'exam_edit') {
+                layer.open({
+                    type: 2,
+                    title: '修改试卷',
+                    shadeClose: false,
+                    area: ['80%', '80%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('exam_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'edit.html?id=' + data.id
+                });
+            }
+            else if (obj.event === 'exam_delete') {
+                layer.confirm('确定要删除该考试吗?', function (index) {
+                    layer.close(index);
+                    admin.req({
+                        url: '/admin/exam/' + data.id + '/'
+                        , type: 'delete'
+                        , done: function (res) {
+                            table.reload('exam_datagrid', {});
+                        }
+                    });
+                });
+            }
+            else if(obj.event === 'exampaper_distribution') {
+                //分配试卷
+                layer.open({
+                    type: 2,
+                    title: '分配试卷',
+                    shadeClose: false,
+                    area: ['95%', '80%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('exam_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'exampaper_edit.html?exam_id=' + data.id
+                });
+            }
+            else if(obj.event === 'examinee_add'){
+                //添加考生
+                layer.open({
+                    type: 2,
+                    title: '添加考生',
+                    shadeClose: false,
+                    area: ['80%', '85%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('exam_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'examinee_edit.html?exam_id=' + data.id
+                });
+            }
+            else if (obj.event === 'exam_detail') {
+                layer.open({
+                    type: 2,
+                    title: '查看详情',
+                    shadeClose: false,
+                    area: ['70%', '65%'],
+                    btn: ['关闭'],
+                    yes: function (index, dom) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'details.html'
+                });
+            }
+        });
+
+        form.on('submit(query-form-element)', function (data) {
+            table.reload('exam_datagrid', {
+                where: data.field
+                , page: {curr: 1}
+            });
+            layer.closeAll();
+            return false
+        });
+
+        $('#exam_add').on('click', function () {
+            layer.open({
+                type: 2,
+                title: '添加试卷',
+                area: ['70%', '90%'],
+                btn: ['保存', '取消'],
+                yes: function (index, dom) {
+                    layui.onSubmitChild = function (res) {
+                        layer.close(index);
+                        table.reload('exam_datagrid', {});
+                    };
+                    layui.submitChild();
+                },
+                btn2: function (index, layero) {
+                    layer.close(index);//关闭当前按钮
+                },
+                content: 'edit.html'
+            });
+        });
+    });
+
+</script>
+</body>
+</html>

+ 203 - 0
uis/admin/examlog/details.html

@@ -0,0 +1,203 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试题管理详情</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+    <style type="text/css">
+        .title {
+            width: 140px;
+            background: #efefef;
+        }
+        .right_anwer{
+            background-color: green;
+            height: 30px;
+            width: 30px;
+            margin: 5px 5px;
+            border: 1px green;
+            border-radius: 100%;
+            text-align: center;
+            line-height: 30px;
+            float: left;
+            color: white;
+        }
+        .wrong_anwer{
+            background-color: red;
+            height: 30px;
+            width: 30px;
+            margin: 5px 5px;
+            border: 1px red;
+            border-radius: 100%;
+            text-align: center;
+            line-height: 30px;
+            float: left;
+            color: white;
+        }
+        .not_done{
+            background-color: lightgrey;
+            height: 30px;
+            width: 30px;
+            margin: 5px 5px;
+            border: 1px lightgrey;
+            border-radius: 100%;
+            text-align: center;
+            line-height: 30px;
+            float: left;
+            color: white;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+
+                    <div id="print_div">
+                        <table class="layui-table">
+
+                            <tr>
+                                <td class="title">名称:</td>
+                                <td id="exam_name" class="cell"></td>
+                                <td class="title">科目:</td>
+                                <td id="exam_subject_name" class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">考试时间:</td>
+                                <td id="exam_time" class="cell"></td>
+                                <td class="title"></td>
+                                <td class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">人员姓名:</td>
+                                <td id="user_name" class="cell"></td>
+                                <td class="title">人员部门:</td>
+                                <td id="user_department" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">分数:</td>
+                                <td id="scores" class="cell"></td>
+                                <td class="title">排名:</td>
+                                <td id="rank" class="cell"></td>
+                            </tr>
+                             <tr>
+                                <td class="title">交卷时间:</td>
+                                <td id="submit_time" class="cell"></td>
+                                <td class="title">用时:</td>
+                                <td id="use_time" class="cell"></td>
+                            </tr>
+                        </table>
+                    </div>
+                    <div id="print_div">
+                        作答概况
+                        <table class="layui-table">
+                            <tr>
+                                <td class="title">题型</td>
+                                <td class="title">题目数</td>
+                                <td class="title">答对数</td>
+                                <td class="title">总分</td>
+                                <td class="title">得分</td>
+                            </tr>
+                            <tr>
+                                <td>单选题</td>
+                                <td class="cell" id="single_count"></td>
+                                <td class="cell" id="single_answer_count"></td>
+                                <td class="cell" id="single_scores"></td>
+                                <td class="cell" id="single_answer_scores"></td>
+                            </tr>
+                            <tr>
+                                <td>多选题</td>
+                                <td class="cell" id="multiple_count"></td>
+                                <td class="cell" id="multiple_answer_count"></td>
+                                <td class="cell" id="multiple_scores"></td>
+                                <td class="cell" id="multiple_answer_scores"></td>
+                            </tr>
+                            <tr>
+                                <td>填空题</td>
+                                <td class="cell" id="fill_count"></td>
+                                <td class="cell" id="fill_answer_count"></td>
+                                <td class="cell" id="fill_scores"></td>
+                                <td class="cell" id="fill_answer_scores"></td>
+                            </tr>
+                            <tr>
+                                <td>判断题</td>
+                                <td class="cell" id="judgment_count"></td>
+                                <td class="cell" id="judgment_answer_count"></td>
+                                <td class="cell" id="judgment_scores"></td>
+                                <td class="cell" id="judgment_answer_scores"></td>
+                            </tr>
+
+
+                        </table>
+                    </div>
+                    <div id="print_div">
+                        作答详情
+                        <span class="layui-badge-dot layui-bg-green"></span>正确
+                        <span class="layui-badge-dot layui-bg-red"></span>错误
+                          <span class="layui-badge-dot layui-bg-gray"></span>未做
+
+                        <div style="margin-top: 10px" id="answer_detail">
+
+                        </div>
+                    </div>
+
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js?t=1"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table'], function () {
+        var $ = layui.$;
+        var table = layui.table
+        , admin = layui.admin;
+
+        var id = layui.view.getParameterByName('id');
+
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        $('.cell').each(function (index, element) {
+            element.innerHTML += (editdata[element.id] || '')
+        });
+
+        admin.req({
+            url: '/admin/exam/examlog/' + id + '/answer_log/',
+            done: function (res) {
+                if (res.code !== 0) {
+                    layer.msg(res.msg);
+                } else {
+                    for (var i=0; i<res.data.length; i++){
+                        var txt = '';
+                        var num = i+1;
+                        if (res.data[i] === 1){
+                            txt = '<span class="right_anwer">'+ num +'</span>'
+                        }else if(res.data[i] === 2){
+                            txt = '<span class="wrong_anwer">' + num + '</span>'
+                        }else if(res.data[i] === 3){
+                            txt = '<span class="not_done">' + num + '</span>'
+                        }
+
+                        $('#answer_detail').append(txt)
+                    }
+                }
+
+
+            }
+        });
+    });
+</script>
+</body>
+</html>

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

@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试卷管理</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=agent-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">
+    <style type="text/css">
+        .seach_items {
+            float: right;
+            margin-left: 10px;
+        }
+    </style>
+    <style type="text/css">
+        .LAY-btns .layui-nav {
+            padding-left: 0;
+            padding-right: 10px;
+            top: -4px;
+            margin: 0 10px;
+            border: 0;
+            background-color: #009688;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-item {
+            line-height: 40px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child {
+            top: 34px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-bar {
+            display: none;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a {
+            color: #333;
+            background-color: #fff;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {
+            background-color: #f2f2f2;
+            color: #000;
+        }
+
+        .tableContent {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            flex-direction: row;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+                    <div class="LAY-btns" style="margin-bottom: 10px;">
+                        <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
+                                        class="layui-icon layui-icon-search"></i>查询
+                                </button>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="exam_name" autocomplete="off" class="layui-input" placeholder="考试名称关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="user_name" autocomplete="off" class="layui-input" placeholder="人员姓名关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <select name="exam_type">
+                                    <option value="" selected>试卷类型</option>
+                                    <option value="1">固定试卷</option>
+                                    <option value="2">随机试卷</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_subject" name="exam_subject">
+                                    <option value="" selected>请选择科目</option>
+                                </select>
+                            </div>
+
+                        </form>
+                        <div style="clear: both;"></div>
+                    </div>
+                    <table class="layui-hide" id="exampaper_datagrid" lay-filter="exam_question-operate"></table>
+
+                    <script type="text/html" id="exam_question-operate-bar">
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="examlog_detail"
+                            >查看</a>
+                        </div>
+                    </script>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'form', 'admin',], function () {
+        var $ = layui.$;
+        var table = layui.table
+            , admin = layui.admin
+            , form = layui.form;
+
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                 form.render();
+            }
+        });
+
+        table.render({
+            elem: '#exampaper_datagrid'
+            , url: '/admin/exam/examlog/'
+            , cols: [[
+                {title: '编号', type: 'numbers'}
+                , {field: 'exam_name', title: '考试名称', width: 200}
+                , {field: 'exam_subject_name', title: '科目', width: 150}
+                , {field: 'exam_time', title: '考试时间', width: 165}
+                , {field: 'user_name', title: '人员姓名', width: 120}
+                , {field: 'user_department', title: '人员部门', width: 120}
+                , {field: 'question_total_scores', title: '总分', width: 80}
+                , {field: 'passline', title: '及格线', width: 80}
+                , {field: 'scores', title: '分数', width: 80}
+                , {field: 'rank', title: '排名', width: 80}
+                , {field: 'submit_time', title: '交卷时间', width: 165}
+                , {field: 'use_time', title: '用时', width: 120}
+                , {title: '操作', width: 80, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
+            ]]
+            , page: true
+            , height: 'full-108'
+        });
+
+        //监听工具条
+        table.on('tool(exam_question-operate)', function (obj) {
+            var data = obj.data;
+            table.editdata = data;
+            if (obj.event === 'examlog_detail') {
+                layer.open({
+                    type: 2,
+                    title: '查看详情',
+                    shadeClose: false,
+                    area: ['70%', '80%'],
+                    btn: ['关闭'],
+                    yes: function (index, dom) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'details.html?id=' + data.id
+                });
+            }
+        });
+
+        form.on('submit(query-form-element)', function (data) {
+            table.reload('exampaper_datagrid', {
+                where: data.field
+                , page: {curr: 1}
+            });
+            layer.closeAll();
+            return false
+        });
+    });
+
+</script>
+</body>
+</html>

+ 113 - 0
uis/admin/exampaper/details.html

@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试题管理详情</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+    <style type="text/css">
+        .title {
+            width: 140px;
+            background: #efefef;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+
+                    <div id="print_div">
+                        <table class="layui-table">
+
+                            <tr>
+                                <td class="title">名称:</td>
+                                <td id="name" class="cell"></td>
+                                <td class="title"></td>
+                                <td class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">科目:</td>
+                                <td id="subject_name" class="cell"></td>
+                                <td class="title">类型:</td>
+                                <td id="type_text" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">总分:</td>
+                                <td id="question_total_scores" class="cell"></td>
+                                <td class="title">及格线:</td>
+                                <td id="passline" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">添加人:</td>
+                                <td id="create_user_text" class="cell"></td>
+                                <td class="title">添加时间:</td>
+                                <td id="create_time" class="cell"></td>
+                            </tr>
+                            <tr id="id_analysis">
+                                <td class="title">单选题:</td>
+                                <td colspan="3" id="single" class="cell"></td>
+                            </tr>
+                            <tr id="id_analysis">
+                                <td class="title">多选题:</td>
+                                <td colspan="3" id="multiple" class="cell"></td>
+                            </tr>
+                            <tr id="id_analysis">
+                                <td class="title">判断题:</td>
+                                <td colspan="3" id="judgment" class="cell"></td>
+                            </tr>
+                            <tr id="id_analysis">
+                                <td class="title">填空题:</td>
+                                <td colspan="3" id="fill" class="cell"></td>
+                            </tr>
+
+                            <tr id="id_analysis">
+                                <td class="title">备注:</td>
+                                <td colspan="3" id="desc" class="cell"></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js?t=1"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table'], function () {
+        var $ = layui.$;
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        $('.cell').each(function (index, element) {
+            element.innerHTML += (editdata[element.id] || '')
+        });
+        var single = '共' + editdata.single_total_count + '题,每题' + editdata.single_scores + '分,简单' + editdata.single_simple_count + '题,' +
+            '中等' + editdata.single_mid_count + '题,困难' + editdata.single_hard_count + '题';
+        var multiple = '共' + editdata.multiple_total_count + '题,每题' + editdata.multiple_scores + '分,简单' + editdata.multiple_simple_count + '题,' +
+            '中等' + editdata.multiple_mid_count + '题,困难' + editdata.multiple_hard_count + '题';
+        var judgment = '共' + editdata.judgment_total_count + '题,每题' + editdata.judgment_scores + '分,简单' + editdata.judgment_simple_count + '题,' +
+            '中等' + editdata.judgment_mid_count + '题,困难' + editdata.judgment_hard_count + '题';
+        var fill = '共' + editdata.fill_total_count + '题,每题' + editdata.fill_scores + '分,简单' + editdata.fill_simple_count + '题,' +
+            '中等' + editdata.fill_mid_count + '题,困难' + editdata.fill_hard_count + '题';
+
+        $('#single').html(single);
+        $('#multiple').html(multiple);
+        $('#judgment').html(judgment);
+        $('#fill').html(fill);
+
+    });
+</script>
+</body>
+</html>

+ 302 - 0
uis/admin/exampaper/edit.html

@@ -0,0 +1,302 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>添加运维宝典</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>名称:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="name" class="layui-input" lay-verify="required">
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>科目:</label>
+                        <div class="layui-input-inline">
+                            <select name="subject" id="id_subject" lay-verify="required">
+                                <option value="">请选择科目</option>
+                            </select>
+                        </div>
+                    </div>
+
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>类型:</label>
+                        <div class="layui-input-inline">
+                            <select id="id_type" name="type" lay-verify="required">
+                                <option value="">请选择类型</option>
+                                <option value="1">模拟试卷</option>
+                                <option value="2">正式试卷</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>及格线:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="passline" lay-verify="intGeZ" class="layui-input">
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <div class="layui-inline" style="width: 100%">
+                        <label class="layui-form-label">单选题:</label>
+                        <div class="layui-inline" style="width: 85%">
+                            每题
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="single_scores" autocomplete="off" class="layui-input">
+                              </div>
+                            分
+                            ,简单
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="single_simple_count" autocomplete="off" class="layui-input single">
+                              </div>
+
+                            ,中等
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="single_mid_count" autocomplete="off" class="layui-input single">
+                              </div>
+                            ,困难
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="single_hard_count" autocomplete="off" class="layui-input single">
+                              </div>
+                            <div class="layui-inline" style="width: 10%">
+                                <label id="id_single_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                              </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="layui-form-item">
+                    <div class="layui-inline" style="width: 100%">
+                        <label class="layui-form-label">多选题:</label>
+                        <div class="layui-inline" style="width: 85%">
+                            每题
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="multiple_scores" autocomplete="off" class="layui-input">
+                              </div>
+                            分
+                            ,简单
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="multiple_simple_count" autocomplete="off" class="layui-input multiple">
+                              </div>
+
+                            ,中等
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="multiple_mid_count" autocomplete="off" class="layui-input multiple">
+                              </div>
+                            ,困难
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="multiple_hard_count" autocomplete="off" class="layui-input multiple">
+                              </div>
+                            <div class="layui-inline" style="width: 10%">
+                                <label id="id_multiple_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                              </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="layui-form-item">
+                    <div class="layui-inline" style="width: 100%">
+                        <label class="layui-form-label">判断题:</label>
+                        <div class="layui-inline" style="width: 85%">
+                            每题
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="judgment_scores" autocomplete="off" class="layui-input">
+                              </div>
+                            分
+                            ,简单
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="judgment_simple_count" autocomplete="off" class="layui-input judgment">
+                              </div>
+
+                            ,中等
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="judgment_mid_count" autocomplete="off" class="layui-input judgment">
+                              </div>
+                            ,困难
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="judgment_hard_count" autocomplete="off" class="layui-input judgment">
+                              </div>
+                            <div class="layui-inline" style="width: 10%">
+                                <label id="id_judgment_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                              </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="layui-form-item">
+                    <div class="layui-inline" style="width: 100%">
+                        <label class="layui-form-label">填空题:</label>
+                        <div class="layui-inline" style="width: 85%">
+                            每题
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="fill_scores" autocomplete="off" class="layui-input">
+                              </div>
+                            分
+                            ,简单
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="fill_simple_count" autocomplete="off" class="layui-input fill">
+                              </div>
+
+                            ,中等
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="fill_mid_count" autocomplete="off" class="layui-input fill">
+                              </div>
+                            ,困难
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="fill_hard_count" autocomplete="off" class="layui-input fill">
+                              </div>
+                            <div class="layui-inline" style="width: 10%">
+                                <label id="id_fill_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                              </div>
+                        </div>
+                    </div>
+                </div>
+
+
+                <div class="layui-form-item">
+                    <label class="layui-form-label">备注:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" name="desc"></textarea>
+                    </div>
+                </div>
+
+                <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index',
+    }).use(['index', 'form', 'utils',], function () {
+        var $ = layui.$
+            , admin = layui.admin
+            , form = layui.form;
+
+        var id = layui.view.getParameterByName('id');
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                loadData();
+                form.render();
+            }
+        });
+        var loadData = function () {
+            if (id) {
+                var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+                form.val("component-form-element", editdata);
+                $('#id_fill_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.fill_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
+                $('#id_judgment_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.judgment_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
+                $('#id_multiple_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.multiple_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
+                $('#id_single_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.single_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
+                $('#id_type').attr('disabled', 'disabled');
+            }
+        };
+
+        $('.single').on("input", function (e) {
+            var total = 0;
+            $('.single').each(function (i) {
+                var target =  $('.single')[i];
+                var val = parseInt(target.value);
+                // 是不是可以在这控制输入框的值 不能是负数之类的
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+             var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_single_total_count').html(txt);
+        });
+        $('.multiple').on("input", function (e) {
+            var total = 0;
+            $('.multiple').each(function (i) {
+                var target =  $('.multiple')[i];
+                var val = parseInt(target.value);
+                // 是不是可以在这控制输入框的值 不能是负数之类的
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+            var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_multiple_total_count').html(txt);
+        });
+        $('.fill').on("input", function (e) {
+            var total = 0;
+            $('.fill').each(function (i) {
+                var target =  $('.fill')[i];
+                var val = parseInt(target.value);
+                // 是不是可以在这控制输入框的值 不能是负数之类的
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+            var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_fill_total_count').html(txt);
+        });
+        $('.judgment').on("input", function (e) {
+            var total = 0;
+            $('.judgment').each(function (i) {
+                var target =  $('.judgment')[i];
+                var val = parseInt(target.value);
+                // 是不是可以在这控制输入框的值 不能是负数之类的
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+            var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_judgment_total_count').html(txt);
+        });
+
+
+        form.render(null, 'component-form-element');
+
+        var url = id ? '/admin/exampaper/' + id + "/" : '/admin/exampaper/',
+            method = id ? 'put' : 'post'
+
+        form.on('submit(component-form-element)', function(data){
+
+          admin.req({
+            url: url
+            , data: data.field
+            , type: method
+            ,done: function(res){
+                parent.layui.onSubmitChild(res.data);
+            }
+          });
+
+          return false;
+        });
+
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+    });
+</script>
+</body>
+</html>

+ 245 - 0
uis/admin/exampaper/index.html

@@ -0,0 +1,245 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试卷管理</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=agent-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">
+    <style type="text/css">
+        .seach_items {
+            float: right;
+            margin-left: 10px;
+        }
+    </style>
+    <style type="text/css">
+        .LAY-btns .layui-nav {
+            padding-left: 0;
+            padding-right: 10px;
+            top: -4px;
+            margin: 0 10px;
+            border: 0;
+            background-color: #009688;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-item {
+            line-height: 40px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child {
+            top: 34px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-bar {
+            display: none;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a {
+            color: #333;
+            background-color: #fff;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {
+            background-color: #f2f2f2;
+            color: #000;
+        }
+
+        .tableContent {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            flex-direction: row;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <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 layui-btn-sm" id="exampaper_add">
+                                <i class="layui-icon layui-icon-add-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
+                                        class="layui-icon layui-icon-search"></i>查询
+                                </button>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="name" autocomplete="off" class="layui-input" placeholder="名称关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <select name="type">
+                                    <option value="" selected>请选择类型</option>
+                                    <option value="1">模拟试卷</option>
+                                    <option value="2">正式试卷</option>
+                                    <option value="3">随机试卷</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_subject" name="subject">
+                                    <option value="" selected>请选择科目</option>
+                                </select>
+                            </div>
+
+                        </form>
+                        <div style="clear: both;"></div>
+                    </div>
+                    <table class="layui-hide" id="exampaper_datagrid" lay-filter="exam_question-operate"></table>
+
+                    <script type="text/html" id="exam_question-operate-bar">
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exampaper_detail"
+                            >查看</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exampaper_edit"
+                            >修改</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exampaper_delete"
+                            >删除</a>
+                        </div>
+                    </script>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'form', 'admin',], function () {
+        var $ = layui.$;
+        var table = layui.table
+            , admin = layui.admin
+            , form = layui.form;
+
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                 form.render();
+            }
+        });
+
+        table.render({
+            elem: '#exampaper_datagrid'
+            , url: '/admin/exampaper/'
+            , cols: [[
+                {title: '编号', type: 'numbers'}
+                , {field: 'name', title: '名称', width: 200}
+                , {field: 'type_text', title: '类型', width: 120}
+                , {field: 'subject_name', title: '科目', width: 120}
+                , {field: 'question_total_scores', title: '总分', width: 80}
+                , {field: 'passline', title: '及格线', width: 80}
+                , {field: 'create_user_text', title: '添加人', width: 100}
+                , {field: 'create_time', title: '添加时间', width: 180}
+                , {title: '操作', width: 180, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
+            ]]
+            , page: true
+            , height: 'full-108'
+        });
+
+        //监听工具条
+        table.on('tool(exam_question-operate)', function (obj) {
+            var data = obj.data;
+            table.editdata = data;
+            if (obj.event === 'exampaper_edit') {
+                layer.open({
+                    type: 2,
+                    title: '修改试卷',
+                    shadeClose: false,
+                    area: ['80%', '80%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('exampaper_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'edit.html?id=' + data.id
+                });
+            }
+            else if (obj.event === 'exampaper_delete') {
+                layer.confirm('确定要删除该试卷吗?', function (index) {
+                    layer.close(index);
+                    admin.req({
+                        url: '/admin/exampaper/' + data.id + '/'
+                        , type: 'delete'
+                        , done: function (res) {
+                            table.reload('exampaper_datagrid', {});
+                        }
+                    });
+                });
+            } else if (obj.event === 'exampaper_detail') {
+                layer.open({
+                    type: 2,
+                    title: '查看详情',
+                    shadeClose: false,
+                    area: ['70%', '65%'],
+                    btn: ['关闭'],
+                    yes: function (index, dom) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'details.html'
+                });
+            }
+        });
+
+        form.on('submit(query-form-element)', function (data) {
+            table.reload('exampaper_datagrid', {
+                where: data.field
+                , page: {curr: 1}
+            });
+            layer.closeAll();
+            return false
+        });
+
+        $('#exampaper_add').on('click', function () {
+            layer.open({
+                type: 2,
+                title: '添加试卷',
+                area: ['90%', '80%'],
+                btn: ['保存', '取消'],
+                yes: function (index, dom) {
+                    layui.onSubmitChild = function (res) {
+                        layer.close(index);
+                        table.reload('exampaper_datagrid', {});
+                    };
+                    layui.submitChild();
+                },
+                btn2: function (index, layero) {
+                    layer.close(index);//关闭当前按钮
+                },
+                content: 'edit.html'
+            });
+        });
+    });
+
+</script>
+</body>
+</html>

+ 131 - 0
uis/admin/examquestion/details.html

@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试题管理详情</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+    <style type="text/css">
+        .title {
+            width: 140px;
+            background: #efefef;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+
+                    <div id="print_div">
+                        <table class="layui-table">
+
+                            <tr>
+                                <td class="title">科目:</td>
+                                <td id="subject_text" class="cell"></td>
+                                <td class="title">章节:</td>
+                                <td id="chapter_text" class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">题型:</td>
+                                <td id="type_text" class="cell"></td>
+                                <td class="title">难度:</td>
+                                <td id="difficulty_text" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">分数:</td>
+                                <td id="scores" class="cell"></td>
+                                <td class="title"></td>
+                                <td class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">录入人:</td>
+                                <td id="create_user_text" class="cell"></td>
+                                <td class="title">录入时间:</td>
+                                <td id="create_time" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">题目:</td>
+                                <td colspan="3" id="title" class="cell"></td>
+                            </tr>
+
+                            <tr id="id_analysis">
+                                <td class="title">解析:</td>
+                                <td colspan="3" id="analysis" class="cell"></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js?t=1"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table'], function () {
+        var $ = layui.$;
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        $('.cell').each(function (index, element) {
+            element.innerHTML += (editdata[element.id] || '')
+        });
+        var answer = ''
+        if (editdata.type < 4) {
+            // 单选、多选、填空
+            var answers = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
+                'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+            var items = editdata.items, answer_title = '', right = ''
+            for (var i = 0; i < items.length; i++) {
+                if (editdata.type === 3) {
+                    answer_title = '空白' + (i + 1)
+                } else {
+                    answer_title = answers[i]
+                }
+
+                if (items[i].right) {
+                    right = '<img src="../right.png" width="15px" height="15px" style="margin-right: 3px">'
+                } else {
+                    right = '<span style="margin-right: 17px"></span>'
+                }
+                if (i === 0) {
+                    answer = "<tr>\n" +
+                        "<td class=\"title\">选项/答案:</td>\n" +
+                        "<td colspan=\"3\" class=\"cell\">" + right + answer_title + "、" + items[i].content + "</td>\n" +
+                        "</tr>"
+                } else {
+                    answer = "<tr>\n" +
+                        "<td></td>\n" +
+                        "<td colspan=\"3\" class=\"cell\">" + right + answer_title + "、" + items[i].content + "</td>\n" +
+                        "</tr>"
+                }
+                $('#id_analysis').before(answer)
+            }
+        } else {
+            // 判断
+            if (editdata.judgment === "1") {
+                right = '<img src="../right.png" width="15px" height="15px" style="margin-right: 3px">正确'
+            } else {
+                right = '<img src="../wrong.png" style="margin-right: 3px">错误'
+            }
+            answer = "<tr>\n" +
+                "<td class=\"title\">选项/答案:</td>\n" +
+                "<td colspan=\"3\" class=\"cell\">" + right + "</td>\n" +
+                "</tr>"
+            $('#id_analysis').before(answer)
+        }
+    });
+</script>
+</body>
+</html>

+ 442 - 0
uis/admin/examquestion/edit.html

@@ -0,0 +1,442 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>添加运维宝典</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>科目:</label>
+                        <div class="layui-input-inline">
+                            <select name="subject" id="id_subject" lay-verify="required" lay-filter="subjectChange">
+                                <option value="">请选择科目</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>章节:</label>
+                        <div class="layui-input-inline">
+                            <select name="chapter" id="id_chapter" lay-verify="required">
+                                <option value="">请选择章节</option>
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>题型:</label>
+                        <div class="layui-input-inline">
+                            <select name="type" lay-verify="required" lay-filter="typeChange" id="id_type">
+                                <option value="">请选择题型</option>
+                                <option value="1">单选题</option>
+                                <option value="2">多选题</option>
+                                <option value="3">填空题</option>
+                                <option value="4">判断题</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>难度:</label>
+                        <div class="layui-input-inline">
+                            <select name="difficulty" lay-verify="required">
+                                <option value="">请选择难度</option>
+                                <option value="1">简单</option>
+                                <option value="2">中等</option>
+                                <option value="3">困难</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>分数:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="scores" lay-verify="number"
+                                   class="layui-input">
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label"><font color='red' size="4">*</font>内容:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" id="content_demo"></textarea>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label"><font color='red' size="4">*</font>选项:</label>
+                    <div class="layui-input-block" id="id_table">
+                        <div class="layui-btn layui-btn-sm layui-btn-normal" id="add_answer">
+                            <i class="layui-icon layui-icon-add "></i> 添加答案
+                        </div>
+                        <div style="height: 5px"></div>
+                        <div id="tableRes" class="table-overlay" style="align-items: center">
+                            <table id="dataTable" lay-filter="dataTable"></table>
+                        </div>
+                    </div>
+                    <div class="layui-input-block layui-hide" id="id_panduan">
+                        <input type="radio" name="judgment" value="1" title="正确" lay-verify="required">
+                        <input type="radio" name="judgment" value="0" title="错误" lay-verify="required">
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">解析:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" name="analysis"></textarea>
+                    </div>
+                </div>
+
+                <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+                <button class="layui-btn" type="button" id="upload_image" style="display: none"></button>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index',
+    }).use(['index', 'form', 'utils', 'table', 'layedit'], function () {
+        var $ = layui.$
+            , admin = layui.admin
+            , table = layui.table
+            , layedit = layui.layedit
+            , form = layui.form;
+
+        var id = layui.view.getParameterByName('id');
+        var chapters = [], chapter_id = ''
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                chapters = res.data.chapter;
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                form.render();
+                loadData()
+            }
+        });
+        var loadData = function () {
+            if (id) {
+                var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+                form.val("component-form-element", editdata);
+                layedit.setContent(editIndex, editdata.title, false);
+                chapter_id = editdata.chapter;
+                subjectChange(editdata.subject)
+                typeChange(editdata.type.toString(), editdata.items)
+            }
+        };
+        var subjectChange = function (value) {
+            var chapter_option = '';
+            for (var i = 0; i < chapters.length; i++) {
+                if (chapters[i].subject_id == parseInt(value)) {
+                    if (chapters[i].id == chapter_id)
+                        chapter_option += "<option value=" + chapters[i].id + " selected>" + chapters[i].name + "</option>";
+                    else
+                        chapter_option += "<option value=" + chapters[i].id + ">" + chapters[i].name + "</option>";
+                }
+            }
+            $("#id_chapter").append(chapter_option);
+            form.render();
+        };
+        form.on('select(subjectChange)', function (data) {
+            $("#id_chapter").html('');
+            if (!data.value) return;
+            subjectChange(data.value)
+        });
+
+        var editIndex = layedit.build('content_demo', {
+            height: 180,
+            tool: [
+                'code', 'strong', 'italic', 'underline', 'del', 'addhr', '|', 'fontFomatt', 'colorpicker', 'face'
+                , '|', 'left', 'center', 'right', '|', 'link', 'unlink', 'anchors'
+                , '|', 'fullScreen'
+            ],
+        });
+        form.render(null, 'component-form-element');
+
+        var url = id ? '/admin/examquestion/' + id + "/" : '/admin/examquestion/',
+            method = id ? 'put' : 'post'
+
+        form.on('submit(component-form-element)', function (data) {
+            data.field.title = layedit.getContent(editIndex);
+            var id_type = data.field.type
+            var oldData = table.cache[layTableId], rows = [], item = {}, error_msg = '', checked = 0
+
+            for (var i = 0; i < oldData.length; i++) {
+                //单选题、多选、填空
+                if (!oldData[i].content) {
+                    error_msg = '选项' + oldData[i].answer + '请填写答案'
+                    break
+                }
+                if (oldData[i].LAY_CHECKED) {
+                    checked += 1
+                }
+                item = {
+                    id: oldData[i].id,
+                    content: oldData[i].content,
+                    right: oldData[i].LAY_CHECKED ? 1 : 0,
+                    order: oldData[i].LAY_TABLE_INDEX,
+                }
+                rows.push(item)
+
+            }
+            if (error_msg) {
+                layer.msg(error_msg, {icon: 2});
+                return false
+            }
+            if (!checked && id_type < 3) {
+                layer.msg('请选择一个正确答案!', {icon: 2});
+                return false
+            }
+            data.field.rows = JSON.stringify(rows)
+            admin.req({
+                url: url
+                , data: data.field
+                , type: method
+                , done: function (res) {
+                    if (res.code != 0) {
+                        layer.msg(res.msg);
+                    } else {
+                        layer.open({
+                            type: 1
+                            , content: '<div style="padding: 20px 100px;">保存成功</div>'
+                            , btn: '关闭'
+                            , btnAlign: 'c' //按钮居中
+                            , shade: 0 //不显示遮罩
+                            , yes: function () {
+                                parent.layer.closeAll('iframe');
+                                parent.layui.table.reload('exam_question_datagrid', {});
+                            }
+                        });
+                    }
+                }
+            });
+            return false;
+        });
+
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+        /////////////////////table
+        var tbWidth = $("#tableRes").width();
+        var cols = [
+            {title: '正确答案', type: 'radio', width: '7%',},
+            {field: 'answer', title: '选项', width: '7%',},
+            {field: 'content', title: '答案', edit: 'text', width: '50%',},
+            {
+                field: 'id', title: '操作', width: '20%', templet: function (d) {
+                    return '<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del" lay-id="' + d.id + '"><i class="layui-icon layui-icon-delete"></i>删除</a>';
+                }
+            }
+        ]
+        var layTableId = "layTable";
+        var tableIns = table.render({
+            elem: '#dataTable',
+            id: layTableId,
+            data: [],
+            width: tbWidth,
+            page: false,
+            limit: 100,
+            loading: true,
+            even: true, //不开启隔行背景
+            cols: [cols],
+            done: function (data, date, total) {
+            }
+        });
+
+        var active = {
+            removeItem: function (index) {
+                var oldData = table.cache[layTableId];
+                oldData.splice(index, 1);    //删除一项
+                var id_type = $('#id_type').val()
+                for (var i = 0; i < oldData.length; i++) {
+                    if (id_type === "1" || id_type === "2") {
+                        //单选题、多选
+                        oldData[i].answer = answers[i]
+                    }
+                    else if (id_type === "3") {
+                        //填空题
+                        oldData[i].answer = '空白' + (parseInt(i) + 1)
+                    }
+                }
+                tableIns.reload({
+                    data: oldData,
+                });
+            },
+        }
+        //激活事件
+        var activeByType = function (type, arg) {
+            if (arguments.length === 2) {
+                active[type] ? active[type].call(this, arg) : '';
+            } else {
+                active[type] ? active[type].call(this) : '';
+            }
+        }
+        table.on('tool(dataTable)', function (obj) {
+            var data = obj.data, event = obj.event, tr = obj.tr; //获得当前行 tr 的DOM对象;
+            var data_index = tr.attr("data-index");
+            switch (event) {
+                case "del":
+                    //obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
+                    activeByType('removeItem', data_index);
+                    layer.msg('删除成功', {icon: 6});
+                    break;
+            }
+        });
+        var answers = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
+            'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+        form.on('select(typeChange)', function (data) {
+            // 更换题型,重新加载table
+            if (!data.value) return;
+            typeChange(data.value)
+        })
+        var typeChange = function (value, items = []) {
+            layui.$('#id_panduan').addClass('layui-hide')
+            layui.$('#id_table').removeClass('layui-hide')
+            var oldData = [], _cols = cols
+            if (value === "1") {
+                //单选题
+                if (items.length > 0) {
+                    for (var i = 0; i < items.length; i++) {
+                        oldData.push(
+                            {
+                                id: items[i].id,
+                                LAY_CHECKED: items[i].right,
+                                answer: answers[i],
+                                content: items[i].content,
+                            }
+                        )
+                    }
+                } else {
+                    for (var i = 0; i < 4; i++) {
+                        oldData.push(
+                            {
+                                id: '',
+                                answer: answers[i],
+                                content: '',
+                            }
+                        )
+                    }
+                }
+                _cols[0] = {title: '正确答案', type: 'radio', width: '7%',}
+            }
+            else if (value === "2") {
+                //多选题
+                if (items.length > 0) {
+                    for (var i = 0; i < items.length; i++) {
+                        oldData.push(
+                            {
+                                id: items[i].id,
+                                LAY_CHECKED: items[i].right,
+                                answer: answers[i],
+                                content: items[i].content,
+                            }
+                        )
+                    }
+                } else {
+                    for (var i = 0; i < 5; i++) {
+                        oldData.push(
+                            {
+                                id: '',
+                                answer: answers[i],
+                                content: '',
+                            }
+                        )
+                    }
+                }
+                _cols[0] = {title: '正确答案', type: 'checkbox', width: '7%',}
+            }
+            else if (value === "3") {
+                //填空题
+                if (items.length > 0) {
+                    for (var i = 0; i < items.length; i++) {
+                        oldData.push(
+                            {
+                                id: items[i].id,
+                                answer: '空白' + items[i].order,
+                                content: items[i].content,
+                            }
+                        )
+                    }
+                } else {
+                    for (var i = 0; i < 3; i++) {
+                        oldData.push(
+                            {
+                                id: '',
+                                answer: '空白' + (i + 1),
+                                content: '',
+                            }
+                        )
+                    }
+                }
+                _cols = _cols.slice(1, 4)
+                    _cols[0] = {field: 'answer', title: '选项', width: '7%',}
+            }
+            else if (value === "4") {
+                //判断题
+                layui.$('#id_panduan').removeClass('layui-hide')
+                layui.$('#id_table').addClass('layui-hide')
+            }
+            tableIns.reload({
+                data: oldData,
+                cols: [_cols],
+            });
+        }
+        $('#add_answer').on('click', function () {
+            var id_type = $('#id_type').val()
+            if (!id_type) {
+                layer.msg("请先选择题型!", {icon: 2})
+                return false
+            }
+
+            var oldData = table.cache[layTableId];
+            var oldData_len = oldData.length
+            if (id_type === "1" || id_type === "2") {
+                //单选题
+                oldData.push(
+                    {
+                        id: '',
+                        answer: answers[oldData_len],
+                        content: '',
+                    }
+                )
+            }
+            else if (id_type === "3") {
+                //填空题
+                oldData.push(
+                    {
+                        id: '',
+                        answer: '空白' + (oldData_len + 1),
+                        content: '',
+                    }
+                )
+            }
+            tableIns.reload({
+                data: oldData,
+            });
+        });
+
+    });
+</script>
+</body>
+</html>

+ 218 - 0
uis/admin/examquestion/index.html

@@ -0,0 +1,218 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试题管理</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=agent-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">
+    <style type="text/css">
+        .seach_items {
+            float: right;
+            margin-left: 10px;
+        }
+    </style>
+    <style type="text/css">
+        .LAY-btns .layui-nav {
+            padding-left: 0;
+            padding-right: 10px;
+            top: -4px;
+            margin: 0 10px;
+            border: 0;
+            background-color: #009688;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-item {
+            line-height: 40px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child {
+            top: 34px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-bar {
+            display: none;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a {
+            color: #333;
+            background-color: #fff;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {
+            background-color: #f2f2f2;
+            color: #000;
+        }
+
+        .tableContent {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            flex-direction: row;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <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 layui-btn-sm" id="exam_question_add">
+                                <i class="layui-icon layui-icon-add-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
+                                        class="layui-icon layui-icon-search"></i>查询
+                                </button>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="title" autocomplete="off" class="layui-input"
+                                       placeholder="名称"/>
+                            </div>
+                        </form>
+                        <div style="clear: both;"></div>
+                    </div>
+                    <table class="layui-hide" id="exam_question_datagrid" lay-filter="exam_question-operate"></table>
+
+                    <script type="text/html" id="exam_question-operate-bar">
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exam_question_detail"
+                            >查看</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exam_question_edit"
+                            >修改</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="exam_question_delete"
+                            >删除</a>
+                        </div>
+                    </script>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'form', 'admin',], function () {
+        var $ = layui.$;
+        var table = layui.table
+            , admin = layui.admin
+            , form = layui.form;
+
+        table.render({
+            elem: '#exam_question_datagrid'
+            , url: '/admin/examquestion/'
+            , cols: [[
+                {title: '编号', type: 'numbers'}
+                , {field: 'subject_text', title: '科目', width: 150}
+                , {field: 'chapter_text', title: '章节', width: 150}
+                , {field: 'type_text', title: '题型', width: 100}
+                , {field: 'title', title: '内容', width: 400}
+                , {field: 'difficulty_text', title: '难度', width: 100}
+                , {field: 'create_user_text', title: '录入人', width: 150}
+                , {field: 'create_time', title: '录入时间', width: 200}
+                , {title: '操作', width: 180, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
+            ]]
+            , page: true
+            , height: 'full-108'
+        });
+
+        //监听工具条
+        table.on('tool(exam_question-operate)', function (obj) {
+            var data = obj.data;
+            table.editdata = data;
+            if (obj.event === 'exam_question_edit') {
+                layer.open({
+                    type: 2,
+                    title: '修改试题',
+                    shadeClose: false,
+                    area: ['80%', '80%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('exam_question_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'edit.html?id=' + data.id
+                });
+            }
+            else if (obj.event === 'exam_question_delete') {
+                layer.confirm('确定要删除该试题吗?', function (index) {
+                    layer.close(index);
+                    admin.req({
+                        url: '/admin/examquestion/' + data.id + '/'
+                        , type: 'delete'
+                        , done: function (res) {
+                            table.reload('exam_question_datagrid', {});
+                        }
+                    });
+                });
+            } else if (obj.event === 'exam_question_detail') {
+                layer.open({
+                    type: 2,
+                    title: '查看详情',
+                    shadeClose: false,
+                    area: ['70%', '80%'],
+                    btn: ['关闭'],
+                    yes: function (index, dom) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'details.html'
+                });
+            }
+        });
+
+        form.on('submit(query-form-element)', function (data) {
+            table.reload('exam_question_datagrid', {
+                where: data.field
+                , page: {curr: 1}
+            });
+            layer.closeAll();
+            return false
+        });
+
+        $('#exam_question_add').on('click', function () {
+            layer.open({
+                type: 2,
+                title: '添加试题',
+                area: ['80%', '90%'],
+                btn: ['保存', '取消'],
+                yes: function (index, dom) {
+                    layui.onSubmitChild = function (res) {
+                        layer.close(index);
+                        table.reload('exam_question_datagrid', {});
+                    };
+                    layui.submitChild();
+                },
+                btn2: function (index, layero) {
+                    layer.close(index);//关闭当前按钮
+                },
+                content: 'edit.html'
+            });
+        });
+    });
+
+</script>
+</body>
+</html>

+ 81 - 0
uis/admin/examquestion_feedback/details.html

@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>知识管理详细</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+    <style type="text/css">
+        .title {
+            width: 140px;
+            background: #efefef;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+
+                    <div id="print_div">
+                        <table class="layui-table">
+
+                            <tr>
+                                <td class="title">题型:</td>
+                                <td id="question_type_text" class="cell"></td>
+                                <td class="title"></td>
+                                <td class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">内容:</td>
+                                <td colspan="3" id="title" class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">错误类型:</td>
+                                <td id="type_text" class="cell"></td>
+                                <td class="title">反馈人</td>
+                                <td id="create_user_text" class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">处理状态:</td>
+                                <td id="status_text" class="cell"></td>
+                                <td class="title">处理时间</td>
+                                <td id="process_time" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">错误描述:</td>
+                                <td colspan="3" id="desc" class="cell"></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js?t=1"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table'], function () {
+        var $ = layui.$;
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        $('.cell').each(function (index, element) {
+            element.innerHTML += (editdata[element.id] || '')
+        });
+    });
+</script>
+</body>
+</html>

+ 435 - 0
uis/admin/examquestion_feedback/edit.html

@@ -0,0 +1,435 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>添加运维宝典</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>科目:</label>
+                        <div class="layui-input-inline">
+                            <select name="subject" id="id_subject" lay-verify="required" lay-filter="subjectChange">
+                                <option value="">请选择科目</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>章节:</label>
+                        <div class="layui-input-inline">
+                            <select name="chapter" id="id_chapter" lay-verify="required">
+                                <option value="">请选择章节</option>
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>题型:</label>
+                        <div class="layui-input-inline">
+                            <select name="type" lay-verify="required" lay-filter="typeChange" id="id_type">
+                                <option value="">请选择题型</option>
+                                <option value="1">单选题</option>
+                                <option value="2">多选题</option>
+                                <option value="3">填空题</option>
+                                <option value="4">判断题</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>难度:</label>
+                        <div class="layui-input-inline">
+                            <select name="difficulty" lay-verify="required">
+                                <option value="">请选择难度</option>
+                                <option value="1">简单</option>
+                                <option value="2">中等</option>
+                                <option value="3">困难</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>分数:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="scores" lay-verify="number"
+                                   class="layui-input">
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label"><font color='red' size="4">*</font>内容:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" id="content_demo"></textarea>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label"><font color='red' size="4">*</font>选项:</label>
+                    <div class="layui-input-block" id="id_table">
+                        <div class="layui-btn layui-btn-sm layui-btn-normal" id="add_answer">
+                            <i class="layui-icon layui-icon-add "></i> 添加答案
+                        </div>
+                        <div style="height: 5px"></div>
+                        <div id="tableRes" class="table-overlay" style="align-items: center">
+                            <table id="dataTable" lay-filter="dataTable"></table>
+                        </div>
+                    </div>
+                    <div class="layui-input-block layui-hide" id="id_panduan">
+                        <input type="radio" name="judgment" value="1" title="正确" lay-verify="required">
+                        <input type="radio" name="judgment" value="0" title="错误" lay-verify="required">
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">解析:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" name="analysis"></textarea>
+                    </div>
+                </div>
+
+                <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+                <button class="layui-btn" type="button" id="upload_image" style="display: none"></button>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index',
+    }).use(['index', 'form', 'utils', 'table', 'layedit'], function () {
+        var $ = layui.$
+            , admin = layui.admin
+            , table = layui.table
+            , layedit = layui.layedit
+            , form = layui.form;
+
+        var id = layui.view.getParameterByName('id');
+        var main_id = layui.view.getParameterByName('main_id');
+        var chapters = [], chapter_id = ''
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                chapters = res.data.chapter;
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                form.render();
+                loadData()
+            }
+        });
+        var loadData = function () {
+            if (main_id) {
+                admin.req({
+                   url: '/admin/examquestion/' + main_id + '/',
+                    done: function (res) {
+                        if (res.code) {
+                            layer.msg(res.msg);
+                        }
+                        form.val("component-form-element", res.data);
+                        layedit.setContent(editIndex, res.data.title, false);
+                        chapter_id = res.data.chapter;
+                        subjectChange(res.data.subject);
+                        typeChange(res.data.type.toString(), res.data.items)
+                    }
+                })
+            }
+        };
+        var subjectChange = function (value) {
+            var chapter_option = '';
+            for (var i = 0; i < chapters.length; i++) {
+                if (chapters[i].subject_id == parseInt(value)) {
+                    if (chapters[i].id == chapter_id)
+                        chapter_option += "<option value=" + chapters[i].id + " selected>" + chapters[i].name + "</option>";
+                    else
+                        chapter_option += "<option value=" + chapters[i].id + ">" + chapters[i].name + "</option>";
+                }
+            }
+            $("#id_chapter").append(chapter_option);
+            form.render();
+        };
+        form.on('select(subjectChange)', function (data) {
+            $("#id_chapter").html('');
+            if (!data.value) return;
+            subjectChange(data.value)
+        });
+
+        var editIndex = layedit.build('content_demo', {
+            height: 180,
+            tool: [
+                'code', 'strong', 'italic', 'underline', 'del', 'addhr', '|', 'fontFomatt', 'colorpicker', 'face'
+                , '|', 'left', 'center', 'right', '|', 'link', 'unlink', 'anchors'
+                , '|', 'fullScreen'
+            ],
+        });
+        form.render(null, 'component-form-element');
+
+        form.on('submit(component-form-element)', function (data) {
+            data.field.title = layedit.getContent(editIndex);
+            var id_type = data.field.type
+            var oldData = table.cache[layTableId], rows = [], item = {}, error_msg = '', checked = 0
+
+            data.field.feedback = id;
+
+            for (var i = 0; i < oldData.length; i++) {
+                //单选题、多选、填空
+                if (!oldData[i].content) {
+                    error_msg = '选项' + oldData[i].answer + '请填写答案'
+                    break
+                }
+                if (oldData[i].LAY_CHECKED) {
+                    checked += 1
+                }
+                item = {
+                    id: oldData[i].id,
+                    content: oldData[i].content,
+                    right: oldData[i].LAY_CHECKED ? 1 : 0,
+                    order: oldData[i].LAY_TABLE_INDEX,
+                }
+                rows.push(item)
+
+            }
+            if (error_msg) {
+                layer.msg(error_msg, {icon: 2});
+                return false
+            }
+            if (!checked && id_type < 3) {
+                layer.msg('请选择一个正确答案!', {icon: 2});
+                return false
+            }
+            data.field.rows = JSON.stringify(rows);
+            admin.req({
+                url: '/admin/examquestion/' + main_id + "/"
+                , data: data.field
+                , type: 'put'
+                , done: function (res) {
+                    parent.layui.onSubmitChild(res.data);
+                }
+            });
+            return false;
+        });
+
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+        /////////////////////table
+        var tbWidth = $("#tableRes").width();
+        var cols = [
+            {title: '正确答案', type: 'radio', width: '7%',},
+            {field: 'answer', title: '选项', width: '7%',},
+            {field: 'content', title: '答案', edit: 'text', width: '50%',},
+            {
+                field: 'id', title: '操作', width: '20%', templet: function (d) {
+                    return '<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del" lay-id="' + d.id + '"><i class="layui-icon layui-icon-delete"></i>删除</a>';
+                }
+            }
+        ]
+        var layTableId = "layTable";
+        var tableIns = table.render({
+            elem: '#dataTable',
+            id: layTableId,
+            data: [],
+            width: tbWidth,
+            page: false,
+            limit: 100,
+            loading: true,
+            even: true, //不开启隔行背景
+            cols: [cols],
+            done: function (data, date, total) {
+            }
+        });
+
+        var active = {
+            removeItem: function (index) {
+                var oldData = table.cache[layTableId];
+                oldData.splice(index, 1);    //删除一项
+                var id_type = $('#id_type').val()
+                for (var i = 0; i < oldData.length; i++) {
+                    if (id_type === "1" || id_type === "2") {
+                        //单选题、多选
+                        oldData[i].answer = answers[i]
+                    }
+                    else if (id_type === "3") {
+                        //填空题
+                        oldData[i].answer = '空白' + (parseInt(i) + 1)
+                    }
+                }
+                tableIns.reload({
+                    data: oldData,
+                });
+            },
+        }
+        //激活事件
+        var activeByType = function (type, arg) {
+            if (arguments.length === 2) {
+                active[type] ? active[type].call(this, arg) : '';
+            } else {
+                active[type] ? active[type].call(this) : '';
+            }
+        }
+        table.on('tool(dataTable)', function (obj) {
+            var data = obj.data, event = obj.event, tr = obj.tr; //获得当前行 tr 的DOM对象;
+            var data_index = tr.attr("data-index");
+            switch (event) {
+                case "del":
+                    //obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
+                    activeByType('removeItem', data_index);
+                    layer.msg('删除成功', {icon: 6});
+                    break;
+            }
+        });
+        var answers = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
+            'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+        form.on('select(typeChange)', function (data) {
+            // 更换题型,重新加载table
+            if (!data.value) return;
+            typeChange(data.value)
+        })
+        var typeChange = function (value, items = []) {
+            layui.$('#id_panduan').addClass('layui-hide')
+            layui.$('#id_table').removeClass('layui-hide')
+            var oldData = [], _cols = cols
+            if (value === "1") {
+                //单选题
+                if (items.length > 0) {
+                    for (var i = 0; i < items.length; i++) {
+                        oldData.push(
+                            {
+                                id: items[i].id,
+                                LAY_CHECKED: items[i].right,
+                                answer: answers[i],
+                                content: items[i].content,
+                            }
+                        )
+                    }
+                } else {
+                    for (var i = 0; i < 4; i++) {
+                        oldData.push(
+                            {
+                                id: '',
+                                answer: answers[i],
+                                content: '',
+                            }
+                        )
+                    }
+                }
+                _cols[0] = {title: '正确答案', type: 'radio', width: '7%',}
+            }
+            else if (value === "2") {
+                //多选题
+                if (items.length > 0) {
+                    for (var i = 0; i < items.length; i++) {
+                        oldData.push(
+                            {
+                                id: items[i].id,
+                                LAY_CHECKED: items[i].right,
+                                answer: answers[i],
+                                content: items[i].content,
+                            }
+                        )
+                    }
+                } else {
+                    for (var i = 0; i < 5; i++) {
+                        oldData.push(
+                            {
+                                id: '',
+                                answer: answers[i],
+                                content: '',
+                            }
+                        )
+                    }
+                }
+                _cols[0] = {title: '正确答案', type: 'checkbox', width: '7%',}
+            }
+            else if (value === "3") {
+                //填空题
+                if (items.length > 0) {
+                    for (var i = 0; i < items.length; i++) {
+                        oldData.push(
+                            {
+                                id: items[i].id,
+                                answer: '空白' + items[i].order,
+                                content: items[i].content,
+                            }
+                        )
+                    }
+                } else {
+                    for (var i = 0; i < 3; i++) {
+                        oldData.push(
+                            {
+                                id: '',
+                                answer: '空白' + (i + 1),
+                                content: '',
+                            }
+                        )
+                    }
+                }
+                _cols = _cols.slice(1, 4)
+                    _cols[0] = {field: 'answer', title: '选项', width: '7%',}
+            }
+            else if (value === "4") {
+                //判断题
+                layui.$('#id_panduan').removeClass('layui-hide')
+                layui.$('#id_table').addClass('layui-hide')
+            }
+            tableIns.reload({
+                data: oldData,
+                cols: [_cols],
+            });
+        }
+        $('#add_answer').on('click', function () {
+            var id_type = $('#id_type').val()
+            if (!id_type) {
+                layer.msg("请先选择题型!", {icon: 2})
+                return false
+            }
+
+            var oldData = table.cache[layTableId];
+            var oldData_len = oldData.length
+            if (id_type === "1" || id_type === "2") {
+                //单选题
+                oldData.push(
+                    {
+                        id: '',
+                        answer: answers[oldData_len],
+                        content: '',
+                    }
+                )
+            }
+            else if (id_type === "3") {
+                //填空题
+                oldData.push(
+                    {
+                        id: '',
+                        answer: '空白' + (oldData_len + 1),
+                        content: '',
+                    }
+                )
+            }
+            tableIns.reload({
+                data: oldData,
+            });
+        });
+
+    });
+</script>
+</body>
+</html>

+ 227 - 0
uis/admin/examquestion_feedback/index.html

@@ -0,0 +1,227 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试题错误反馈</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=agent-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">
+    <style type="text/css">
+        .seach_items {
+            float: right;
+            margin-left: 10px;
+        }
+    </style>
+    <style type="text/css">
+        .LAY-btns .layui-nav {
+            padding-left: 0;
+            padding-right: 10px;
+            top: -4px;
+            margin: 0 10px;
+            border: 0;
+            background-color: #009688;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-item {
+            line-height: 40px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child {
+            top: 34px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-bar {
+            display: none;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a {
+            color: #333;
+            background-color: #fff;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {
+            background-color: #f2f2f2;
+            color: #000;
+        }
+
+        .tableContent {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            flex-direction: row;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+                    <div class="LAY-btns" style="margin-bottom: 10px;">
+                        <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
+                                        class="layui-icon layui-icon-search"></i>查询
+                                </button>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="title" autocomplete="off" class="layui-input" placeholder="题目关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_status" name="status">
+                                    <option value="" selected>处理状态</option>
+                                    <option value="1">未处理</option>
+                                    <option value="2">已处理</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_chapter" name="chapter">
+                                    <option value="" selected>请选择章节</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_subject" name="subject" lay-filter="subjectChange">
+                                    <option value="" selected>请选择科目</option>
+                                </select>
+                            </div>
+                        </form>
+                        <div style="clear: both;"></div>
+                    </div>
+                    <table class="layui-hide" id="knowledge_datagrid" lay-filter="exam_question-operate"></table>
+
+                    <script type="text/html" id="exam_question-operate-bar">
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="knowledge_detail"
+                            >查看</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="knowledge_edit"
+                            >修改试题</a>
+                        </div>
+                    </script>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'form', 'admin',], function () {
+        var $ = layui.$;
+        var table = layui.table
+            , admin = layui.admin
+            , form = layui.form;
+
+        table.render({
+            elem: '#knowledge_datagrid'
+            , url: '/admin/examquestion/feedback/'
+            , cols: [[
+                {title: '编号', type: 'numbers'}
+                , {field: 'question_type_text', title: '题型', width: 100}
+                , {field: 'title', title: '内容', width: 400}
+                , {field: 'type_text', title: '错误类型', width: 90}
+                , {field: 'desc', title: '错误描述', width: 300}
+                , {field: 'create_user_text', title: '反馈人', width: 80}
+                , {field: 'status_text', title: '处理状态', width: 90}
+                , {field: 'process_time', title: '处理时间', width: 200}
+                , {title: '操作', width: 160, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
+            ]]
+            , page: true
+            , height: 'full-108'
+        });
+
+        var chapters = [], chapter_id = '';
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                chapters = res.data.chapter;
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                form.render();
+            }
+        });
+
+        var subjectChange = function (value) {
+            var chapter_option = '';
+            for (var i = 0; i < chapters.length; i++) {
+                if (chapters[i].subject_id === parseInt(value)) {
+                    chapter_option += "<option value=" + chapters[i].id + ">" + chapters[i].name + "</option>";
+                }
+            }
+            $("#id_chapter").append(chapter_option);
+            form.render();
+        };
+        form.on('select(subjectChange)', function (data) {
+            $("#id_chapter").html('<option value="" selected>请选择章节</option>');
+            if (!data.value) return;
+            subjectChange(data.value)
+        });
+
+
+        //监听工具条
+        table.on('tool(exam_question-operate)', function (obj) {
+            var data = obj.data;
+            table.editdata = data;
+            if (obj.event === 'knowledge_edit') {
+                layer.open({
+                    type: 2,
+                    title: '修改试题',
+                    shadeClose: false,
+                    area: ['80%', '80%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('knowledge_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'edit.html?id=' + data.id + '&main_id=' + data.main
+                });
+            } else if (obj.event === 'knowledge_detail') {
+                layer.open({
+                    type: 2,
+                    title: '查看详情',
+                    shadeClose: false,
+                    area: ['70%', '80%'],
+                    btn: ['关闭'],
+                    yes: function (index, dom) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'details.html'
+                });
+            }
+        });
+
+        form.on('submit(query-form-element)', function (data) {
+            table.reload('knowledge_datagrid', {
+                where: data.field
+                , page: {curr: 1}
+            });
+            layer.closeAll();
+            return false
+        });
+
+    });
+
+</script>
+</body>
+</html>

+ 61 - 14
uis/admin/index.html

@@ -2,7 +2,7 @@
 <html>
 <head>
   <meta charset="utf-8">
-  <title>燎原管理系统</title>
+  <title>人员能力评估后台管理系统</title>
   <meta name="renderer" content="webkit">
   <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
   <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
@@ -10,7 +10,7 @@
   <link rel="stylesheet" href="../layuiadmin/style/admin.css" media="all">
 </head>
 <body class="layui-layout-body">
-  
+
   <div id="LAY_app">
     <div class="layui-layout layui-layout-admin">
       <div class="layui-header">
@@ -52,32 +52,80 @@
           </li>
         </ul>
       </div>
-      
+
       <!-- 侧边菜单 -->
       <div class="layui-side layui-side-menu">
         <div class="layui-side-scroll">
           <div class="layui-logo">
-            <span>燎原管理系统</span>
+            <span>人员能力评估后台管理系统</span>
           </div>
 
           <ul class="layui-nav layui-nav-tree" lay-shrink="all" id="LAY-system-side-muen" lay-filter="layadmin-system-side-menu">
             <li data-name="set" class="layui-nav-item">
-              <a href="javascript:;" lay-tips="基础信息" lay-direction="2">
+              <a href="javascript:;" lay-tips="试题库管理" lay-direction="2">
                 <i class="layui-icon layui-icon-set"></i>
-                <cite>基础信息</cite>
+                <cite>试题库管理</cite>
               </a>
               <dl class="layui-nav-child">
                 <dd data-name="nav">
-                  <a lay-href="user/index.html">用户管理</a>
+                  <a lay-href="examquestion/index.html">试题管理</a>
                 </dd>
                 <dd data-name="nav">
-                  <a lay-href="tenant/index.html">租户管理</a>
+                  <a lay-href="examquestion_feedback/index.html">错误反馈</a>
                 </dd>
+              </dl>
+            </li>
+            <li data-name="set" class="layui-nav-item">
+              <a href="javascript:;" lay-tips="知识库管理" lay-direction="2">
+                <i class="layui-icon layui-icon-set"></i>
+                <cite>知识库管理</cite>
+              </a>
+              <dl class="layui-nav-child">
                 <dd data-name="nav">
-                  <a lay-href="applet/index.html">小程序管理</a>
+                  <a lay-href="knowledge/index.html">知识管理</a>
                 </dd>
                 <dd data-name="nav">
-                  <a lay-href="third_party/index.html">第三方设置</a>
+                  <a lay-href="knowledge_feedback/index.html">错误反馈</a>
+                </dd>
+              </dl>
+            </li>
+            <li data-name="set" class="layui-nav-item">
+              <a href="javascript:;" lay-tips="考试管理" lay-direction="2">
+                <i class="layui-icon layui-icon-set"></i>
+                <cite>考试管理</cite>
+              </a>
+              <dl class="layui-nav-child">
+                <dd data-name="nav">
+                  <a lay-href="exampaper/index.html">试卷管理</a>
+                </dd>
+                <dd data-name="nav">
+                  <a lay-href="exam/index.html">考试管理</a>
+                </dd>
+                <dd data-name="nav">
+                  <a lay-href="examlog/index.html">考试记录</a>
+                </dd>
+              </dl>
+            </li>
+            <li data-name="set" class="layui-nav-item">
+              <a href="javascript:;" lay-tips="系统管理" lay-direction="2">
+                <i class="layui-icon layui-icon-set"></i>
+                <cite>系统管理</cite>
+              </a>
+              <dl class="layui-nav-child">
+               <dd data-name="nav">
+                  <a lay-href="subject/index.html">科目章节设置</a>
+                </dd>
+                <dd data-name="nav">
+                  <a lay-href="department/index.html">部门管理</a>
+                </dd>
+                <dd data-name="nav">
+                  <a lay-href="user/index.html">账户管理</a>
+                </dd>
+                <!--<dd data-name="nav">
+                  <a lay-href="applet/index.html">权限管理</a>
+                </dd>-->
+                <dd data-name="nav">
+                  <a lay-href="log/index.html">系统日志</a>
                 </dd>
               </dl>
             </li>
@@ -107,15 +155,14 @@
           </ul>
         </div>
       </div>
-      
-      
+
       <!-- 主体内容 -->
       <div class="layui-body" id="LAY_app_body">
         <div class="layadmin-tabsbody-item layui-show">
-          <iframe src="dashboard/index.html" frameborder="0" class="layadmin-iframe"></iframe>
+          <iframe src="dashboard/home.html" frameborder="0" class="layadmin-iframe"></iframe>
         </div>
       </div>
-      
+
       <!-- 辅助元素,一般用于移动设备下遮罩 -->
       <div class="layadmin-body-shade" layadmin-event="shade"></div>
     </div>

+ 74 - 0
uis/admin/knowledge/details.html

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>知识管理详细</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+    <style type="text/css">
+        .title {
+            width: 140px;
+            background: #efefef;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+
+                    <div id="print_div">
+                        <table class="layui-table">
+
+                            <tr>
+                                <td class="title">科目:</td>
+                                <td id="subject_name" class="cell"></td>
+                                <td class="title">章节:</td>
+                                <td id="chapter_name" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">名称:</td>
+                                <td id="name" class="cell"></td>
+                                <td class="title">添加时间</td>
+                                <td id="create_time" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">识别特征:</td>
+                                <td colspan="3" id="feature" class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">备注:</td>
+                                <td colspan="3" id="desc" class="cell"></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js?t=1"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table'], function () {
+        var $ = layui.$;
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        $('.cell').each(function (index, element) {
+            element.innerHTML += (editdata[element.id] || '')
+        });
+    });
+</script>
+</body>
+</html>

+ 164 - 0
uis/admin/knowledge/edit.html

@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>知识管理添加、修改</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>科目:</label>
+                        <div class="layui-input-inline">
+                            <select name="subject" id="id_subject" lay-verify="required" lay-filter="subjectChange">
+                                <option value="">请选择科目</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>章节:</label>
+                        <div class="layui-input-inline">
+                            <select name="chapter" id="id_chapter" lay-verify="required">
+                                <option value="">请选择章节</option>
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>名称:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="name" lay-verify="required" class="layui-input">
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label"><font color='red' size="4">*</font>识别特征:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" id="content_demo"></textarea>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">备注:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" name="desc"></textarea>
+                    </div>
+                </div>
+
+                <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+                <button class="layui-btn" type="button" id="upload_image" style="display: none"></button>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index',
+    }).use(['index', 'form', 'utils', 'table', 'layedit'], function () {
+        var $ = layui.$
+            , admin = layui.admin
+            , table = layui.table
+            , layedit = layui.layedit
+            , form = layui.form;
+
+        var id = layui.view.getParameterByName('id');
+        var chapters = [], chapter_id = '';
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                chapters = res.data.chapter;
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                form.render();
+                loadData()
+            }
+        });
+        var loadData = function () {
+            if (id) {
+                var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+                form.val("component-form-element", editdata);
+                layedit.setContent(editIndex, editdata.feature, false);
+                chapter_id = editdata.chapter;
+                subjectChange(editdata.subject);
+            }
+        };
+        var subjectChange = function (value) {
+            var chapter_option = '';
+            for (var i = 0; i < chapters.length; i++) {
+                if (chapters[i].subject_id === parseInt(value)) {
+                    if (chapters[i].id === chapter_id)
+                        chapter_option += "<option value=" + chapters[i].id + " selected>" + chapters[i].name + "</option>";
+                    else
+                        chapter_option += "<option value=" + chapters[i].id + ">" + chapters[i].name + "</option>";
+                }
+            }
+            $("#id_chapter").append(chapter_option);
+            form.render();
+        };
+        form.on('select(subjectChange)', function (data) {
+            $("#id_chapter").html('<option value="">请选择章节</option>');
+            if (!data.value) return;
+            subjectChange(data.value)
+        });
+
+        var editIndex = layedit.build('content_demo', {
+            height: 200,
+            tool: [
+                'code', 'strong', 'italic', 'underline', 'del', 'addhr', '|', 'fontFomatt', 'colorpicker', 'face'
+                , '|', 'left', 'center', 'right', '|', 'link', 'unlink', 'anchors'
+                , '|', 'fullScreen'
+            ],
+        });
+        form.render(null, 'component-form-element');
+
+        var url = id ? '/admin/knowledge/' + id + "/" : '/admin/knowledge/',
+            method = id ? 'put' : 'post';
+
+        form.on('submit(component-form-element)', function (data) {
+            data.field.feature = layedit.getContent(editIndex);
+            if(!data.field.feature){
+                layer.msg('知识特征不能为空!', {icon: 2});
+                return false
+            }
+
+            admin.req({
+                url: url
+                , data: data.field
+                , type: method
+                , done: function (res) {
+                    parent.layui.onSubmitChild(res.data);
+                }
+            });
+            return false;
+        });
+
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+
+    });
+</script>
+</body>
+</html>

+ 258 - 0
uis/admin/knowledge/index.html

@@ -0,0 +1,258 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>知识管理</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=agent-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">
+    <style type="text/css">
+        .seach_items {
+            float: right;
+            margin-left: 10px;
+        }
+    </style>
+    <style type="text/css">
+        .LAY-btns .layui-nav {
+            padding-left: 0;
+            padding-right: 10px;
+            top: -4px;
+            margin: 0 10px;
+            border: 0;
+            background-color: #009688;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-item {
+            line-height: 40px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child {
+            top: 34px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-bar {
+            display: none;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a {
+            color: #333;
+            background-color: #fff;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {
+            background-color: #f2f2f2;
+            color: #000;
+        }
+
+        .tableContent {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            flex-direction: row;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <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 layui-btn-sm" id="knowledge_add">
+                                <i class="layui-icon layui-icon-add-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
+                                        class="layui-icon layui-icon-search"></i>查询
+                                </button>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="name" autocomplete="off" class="layui-input" placeholder="名称关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_chapter" name="chapter">
+                                    <option value="" selected>请选择章节</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_subject" name="subject" lay-filter="subjectChange">
+                                    <option value="" selected>请选择科目</option>
+                                </select>
+                            </div>
+                        </form>
+                        <div style="clear: both;"></div>
+                    </div>
+                    <table class="layui-hide" id="knowledge_datagrid" lay-filter="exam_question-operate"></table>
+
+                    <script type="text/html" id="exam_question-operate-bar">
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="knowledge_detail"
+                            >查看</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="knowledge_edit"
+                            >修改</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="knowledge_delete"
+                            >删除</a>
+                        </div>
+                    </script>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'form', 'admin',], function () {
+        var $ = layui.$;
+        var table = layui.table
+            , admin = layui.admin
+            , form = layui.form;
+
+        table.render({
+            elem: '#knowledge_datagrid'
+            , url: '/admin/knowledge/'
+            , cols: [[
+                {title: '编号', type: 'numbers'}
+                , {field: 'subject_name', title: '科目', width: 150}
+                , {field: 'chapter_name', title: '章节', width: 150}
+                , {field: 'name', title: '名称', width: 300}
+                , {field: 'create_user_text', title: '录入人', width: 150}
+                , {field: 'create_time', title: '录入时间', width: 200}
+                , {title: '操作', width: 160, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
+            ]]
+            , page: true
+            , height: 'full-108'
+        });
+
+        var chapters = [], chapter_id = '';
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                chapters = res.data.chapter;
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                form.render();
+            }
+        });
+
+        var subjectChange = function (value) {
+            var chapter_option = '';
+            for (var i = 0; i < chapters.length; i++) {
+                if (chapters[i].subject_id === parseInt(value)) {
+                    chapter_option += "<option value=" + chapters[i].id + ">" + chapters[i].name + "</option>";
+                }
+            }
+            $("#id_chapter").append(chapter_option);
+            form.render();
+        };
+        form.on('select(subjectChange)', function (data) {
+            $("#id_chapter").html('<option value="" selected>请选择章节</option>');
+            if (!data.value) return;
+            subjectChange(data.value)
+        });
+
+
+        //监听工具条
+        table.on('tool(exam_question-operate)', function (obj) {
+            var data = obj.data;
+            table.editdata = data;
+            if (obj.event === 'knowledge_edit') {
+                layer.open({
+                    type: 2,
+                    title: '修改知识',
+                    shadeClose: false,
+                    area: ['80%', '80%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('knowledge_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'edit.html?id=' + data.id
+                });
+            }
+            else if (obj.event === 'knowledge_delete') {
+                layer.confirm('确定要删除该知识吗?', function (index) {
+                    layer.close(index);
+                    admin.req({
+                        url: '/admin/knowledge/' + data.id + '/'
+                        , type: 'delete'
+                        , done: function (res) {
+                            table.reload('knowledge_datagrid', {});
+                        }
+                    });
+                });
+            } else if (obj.event === 'knowledge_detail') {
+                layer.open({
+                    type: 2,
+                    title: '查看详情',
+                    shadeClose: false,
+                    area: ['70%', '80%'],
+                    btn: ['关闭'],
+                    yes: function (index, dom) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'details.html'
+                });
+            }
+        });
+
+        form.on('submit(query-form-element)', function (data) {
+            table.reload('knowledge_datagrid', {
+                where: data.field
+                , page: {curr: 1}
+            });
+            layer.closeAll();
+            return false
+        });
+
+        $('#knowledge_add').on('click', function () {
+            layer.open({
+                type: 2,
+                title: '添加知识',
+                area: ['80%', '90%'],
+                btn: ['保存', '取消'],
+                yes: function (index, dom) {
+                    layui.onSubmitChild = function (res) {
+                        layer.close(index);
+                        table.reload('knowledge_datagrid', {});
+                    };
+                    layui.submitChild();
+                },
+                btn2: function (index, layero) {
+                    layer.close(index);//关闭当前按钮
+                },
+                content: 'edit.html'
+            });
+        });
+    });
+
+</script>
+</body>
+</html>

+ 83 - 0
uis/admin/knowledge_feedback/details.html

@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>知识管理详细</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+    <style type="text/css">
+        .title {
+            width: 140px;
+            background: #efefef;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+
+                    <div id="print_div">
+                        <table class="layui-table">
+
+                            <tr>
+                                <td class="title">名称:</td>
+                                <td id="name" class="cell"></td>
+                                <td class="title"></td>
+                                <td class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">科目:</td>
+                                <td id="subject_name" class="cell"></td>
+                                <td class="title">章节:</td>
+                                <td id="chapter_name" class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">错误类型:</td>
+                                <td id="type_text" class="cell"></td>
+                                <td class="title">反馈人</td>
+                                <td id="create_user_text" class="cell"></td>
+                            </tr>
+
+                            <tr>
+                                <td class="title">处理状态:</td>
+                                <td id="status_text" class="cell"></td>
+                                <td class="title">处理时间</td>
+                                <td id="process_time" class="cell"></td>
+                            </tr>
+                            <tr>
+                                <td class="title">错误描述:</td>
+                                <td colspan="3" id="desc" class="cell"></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js?t=1"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table'], function () {
+        var $ = layui.$;
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        $('.cell').each(function (index, element) {
+            element.innerHTML += (editdata[element.id] || '')
+        });
+    });
+</script>
+</body>
+</html>

+ 170 - 0
uis/admin/knowledge_feedback/edit.html

@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>知识管理添加、修改</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>科目:</label>
+                        <div class="layui-input-inline">
+                            <select name="subject" id="id_subject" lay-verify="required" lay-filter="subjectChange">
+                                <option value="">请选择科目</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>章节:</label>
+                        <div class="layui-input-inline">
+                            <select name="chapter" id="id_chapter" lay-verify="required">
+                                <option value="">请选择章节</option>
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <label class="layui-form-label"><font color='red' size="4">*</font>名称:</label>
+                        <div class="layui-input-inline">
+                            <input type="text" name="name" lay-verify="required" class="layui-input">
+                        </div>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label"><font color='red' size="4">*</font>识别特征:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" id="content_demo"></textarea>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">备注:</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" placeholder="请输内容" name="desc"></textarea>
+                    </div>
+                </div>
+
+                <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+                <button class="layui-btn" type="button" id="upload_image" style="display: none"></button>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index',
+    }).use(['index', 'form', 'utils', 'table', 'layedit'], function () {
+        var $ = layui.$
+            , admin = layui.admin
+            , table = layui.table
+            , layedit = layui.layedit
+            , form = layui.form;
+
+        var id = layui.view.getParameterByName('id');
+        var main_id = layui.view.getParameterByName('main_id');
+        var chapters = [], chapter_id = '';
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                chapters = res.data.chapter;
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                form.render();
+                loadData()
+            }
+        });
+        var loadData = function () {
+            if (main_id) {
+                admin.req({
+                   url: '/admin/knowledge/' + main_id + '/',
+                    done: function (res) {
+                        if (res.code) {
+                            layer.msg(res.msg);
+                        }
+                        form.val("component-form-element", res.data);
+                        layedit.setContent(editIndex, res.data.feature, false);
+                        chapter_id = res.data.chapter;
+                        subjectChange(res.data.subject);
+                    }
+                })
+            }
+        };
+        var subjectChange = function (value) {
+            var chapter_option = '';
+            for (var i = 0; i < chapters.length; i++) {
+                if (chapters[i].subject_id === parseInt(value)) {
+                    if (chapters[i].id === chapter_id)
+                        chapter_option += "<option value=" + chapters[i].id + " selected>" + chapters[i].name + "</option>";
+                    else
+                        chapter_option += "<option value=" + chapters[i].id + ">" + chapters[i].name + "</option>";
+                }
+            }
+            $("#id_chapter").append(chapter_option);
+            form.render();
+        };
+        form.on('select(subjectChange)', function (data) {
+            $("#id_chapter").html('<option value="">请选择章节</option>');
+            if (!data.value) return;
+            subjectChange(data.value)
+        });
+
+        var editIndex = layedit.build('content_demo', {
+            height: 200,
+            tool: [
+                'code', 'strong', 'italic', 'underline', 'del', 'addhr', '|', 'fontFomatt', 'colorpicker', 'face'
+                , '|', 'left', 'center', 'right', '|', 'link', 'unlink', 'anchors'
+                , '|', 'fullScreen'
+            ],
+        });
+        form.render(null, 'component-form-element');
+
+        form.on('submit(component-form-element)', function (data) {
+            data.field.feature = layedit.getContent(editIndex);
+            if(!data.field.feature){
+                layer.msg('知识特征不能为空!', {icon: 2});
+                return false
+            }
+            data.field.feedback = id;
+
+            admin.req({
+                url:  '/admin/knowledge/' + main_id + "/"
+                , data: data.field
+                , type: 'put'
+                , done: function (res) {
+                    parent.layui.onSubmitChild(res.data);
+                }
+            });
+            return false;
+        });
+
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+
+    });
+</script>
+</body>
+</html>

+ 228 - 0
uis/admin/knowledge_feedback/index.html

@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>知识错误反馈</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=agent-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">
+    <style type="text/css">
+        .seach_items {
+            float: right;
+            margin-left: 10px;
+        }
+    </style>
+    <style type="text/css">
+        .LAY-btns .layui-nav {
+            padding-left: 0;
+            padding-right: 10px;
+            top: -4px;
+            margin: 0 10px;
+            border: 0;
+            background-color: #009688;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-item {
+            line-height: 40px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child {
+            top: 34px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-bar {
+            display: none;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a {
+            color: #333;
+            background-color: #fff;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {
+            background-color: #f2f2f2;
+            color: #000;
+        }
+
+        .tableContent {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            flex-direction: row;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+                    <div class="LAY-btns" style="margin-bottom: 10px;">
+                        <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
+                                        class="layui-icon layui-icon-search"></i>查询
+                                </button>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="name" autocomplete="off" class="layui-input" placeholder="名称关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_status" name="status">
+                                    <option value="" selected>处理状态</option>
+                                    <option value="1">未处理</option>
+                                    <option value="2">已处理</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_chapter" name="chapter">
+                                    <option value="" selected>请选择章节</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <select id="id_subject" name="subject" lay-filter="subjectChange">
+                                    <option value="" selected>请选择科目</option>
+                                </select>
+                            </div>
+                        </form>
+                        <div style="clear: both;"></div>
+                    </div>
+                    <table class="layui-hide" id="knowledge_datagrid" lay-filter="exam_question-operate"></table>
+
+                    <script type="text/html" id="exam_question-operate-bar">
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="knowledge_detail"
+                            >查看</a>
+                        </div>
+                        <div class="layui-btn-group">
+                            <a class="layui-btn layui-btn-xs" lay-event="knowledge_edit"
+                            >修改知识</a>
+                        </div>
+                    </script>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'form', 'admin',], function () {
+        var $ = layui.$;
+        var table = layui.table
+            , admin = layui.admin
+            , form = layui.form;
+
+        table.render({
+            elem: '#knowledge_datagrid'
+            , url: '/admin/knowledge/feedback/'
+            , cols: [[
+                {title: '编号', type: 'numbers'}
+                , {field: 'subject_name', title: '科目', width: 150}
+                , {field: 'chapter_name', title: '章节', width: 150}
+                , {field: 'name', title: '名称', width: 150}
+                , {field: 'type_text', title: '错误类型', width: 90}
+                , {field: 'desc', title: '错误描述', width: 300}
+                , {field: 'create_user_text', title: '反馈人', width: 80}
+                , {field: 'status_text', title: '处理状态', width: 90}
+                , {field: 'process_time', title: '处理时间', width: 200}
+                , {title: '操作', width: 160, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
+            ]]
+            , page: true
+            , height: 'full-108'
+        });
+
+        var chapters = [], chapter_id = '';
+        admin.req({
+            url: '/admin/subject/dict/',
+            done: function (res) {
+                chapters = res.data.chapter;
+                var subject = res.data.subject;
+                var subject_node = $('#id_subject');
+                for (var i in subject) {
+                    var pid = subject[i].id;
+                    var subject_value = subject[i].name;
+                    subject_node.append("<option value='" + pid + "'>" + subject_value + "</option>");
+                }
+                form.render();
+            }
+        });
+
+        var subjectChange = function (value) {
+            var chapter_option = '';
+            for (var i = 0; i < chapters.length; i++) {
+                if (chapters[i].subject_id === parseInt(value)) {
+                    chapter_option += "<option value=" + chapters[i].id + ">" + chapters[i].name + "</option>";
+                }
+            }
+            $("#id_chapter").append(chapter_option);
+            form.render();
+        };
+        form.on('select(subjectChange)', function (data) {
+            $("#id_chapter").html('<option value="" selected>请选择章节</option>');
+            if (!data.value) return;
+            subjectChange(data.value)
+        });
+
+
+        //监听工具条
+        table.on('tool(exam_question-operate)', function (obj) {
+            var data = obj.data;
+            table.editdata = data;
+            if (obj.event === 'knowledge_edit') {
+                layer.open({
+                    type: 2,
+                    title: '修改知识',
+                    shadeClose: false,
+                    area: ['80%', '80%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('knowledge_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'edit.html?id=' + data.id + '&main_id=' + data.main
+                });
+            } else if (obj.event === 'knowledge_detail') {
+                layer.open({
+                    type: 2,
+                    title: '查看详情',
+                    shadeClose: false,
+                    area: ['70%', '70%'],
+                    btn: ['关闭'],
+                    yes: function (index, dom) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'details.html'
+                });
+            }
+        });
+
+        form.on('submit(query-form-element)', function (data) {
+            table.reload('knowledge_datagrid', {
+                where: data.field
+                , page: {curr: 1}
+            });
+            layer.closeAll();
+            return false
+        });
+
+    });
+
+</script>
+</body>
+</html>

+ 148 - 0
uis/admin/log/index.html

@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>试卷管理</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=agent-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">
+    <style type="text/css">
+        .seach_items {
+            float: right;
+            margin-left: 10px;
+        }
+    </style>
+    <style type="text/css">
+        .LAY-btns .layui-nav {
+            padding-left: 0;
+            padding-right: 10px;
+            top: -4px;
+            margin: 0 10px;
+            border: 0;
+            background-color: #009688;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-item {
+            line-height: 40px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child {
+            top: 34px;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-bar {
+            display: none;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a {
+            color: #333;
+            background-color: #fff;
+        }
+
+        .LAY-btns .layui-nav .layui-nav-child dd.layui-this a:hover {
+            background-color: #f2f2f2;
+            color: #000;
+        }
+
+        .tableContent {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            flex-direction: row;
+        }
+    </style>
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <div class="layui-row layui-col-space15">
+                <div class="layui-col-md12">
+                    <div class="LAY-btns" style="margin-bottom: 10px;">
+                        <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
+                                        class="layui-icon layui-icon-search"></i>查询
+                                </button>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="description" autocomplete="off" class="layui-input" placeholder="内容关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" name="name" autocomplete="off" class="layui-input" placeholder="姓名关键字"/>
+                            </div>
+                            <div class="seach_items">
+                                <select name="type">
+                                    <option value="" selected>操作类型</option>
+                                    <option value="1">添加</option>
+                                    <option value="2">修改</option>
+                                    <option value="3">删除</option>
+                                </select>
+                            </div>
+                            <div class="seach_items">
+                                <input type="text" class="layui-input" name="create_time" id="create_time" placeholder=" - ">
+                            </div>
+
+                        </form>
+                        <div style="clear: both;"></div>
+                    </div>
+                    <table class="layui-hide" id="log_datagrid"></table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'form', 'admin', 'laydate', ], function () {
+        var $ = layui.$;
+        var table = layui.table
+            ,laydate = layui.laydate
+            , admin = layui.admin
+            , form = layui.form;
+
+        laydate.render({
+        elem: '#create_time'
+        ,range: true
+    });
+
+        table.render({
+            elem: '#log_datagrid'
+            , url: '/admin/syslog/'
+            , cols: [[
+                {title: '编号', type: 'numbers'}
+                , {field: 'username', title: '用户名', width: 120}
+                , {field: 'user_name', title: '姓名', width: 120}
+                , {field: 'user_department', title: '部门', width: 120}
+                , {field: 'type_text', title: '操作类别', width: 120}
+                , {field: 'create_time', title: '时间', width: 160}
+                , {field: 'description', title: '操作内容'}
+            ]]
+            , page: true
+            , height: 'full-108'
+        });
+
+
+        form.on('submit(query-form-element)', function (data) {
+            table.reload('log_datagrid', {
+                where: data.field
+                , page: {curr: 1}
+            });
+            layer.closeAll();
+            return false
+        });
+
+    });
+
+</script>
+</body>
+</html>

+ 98 - 94
uis/admin/login/login.html

@@ -1,118 +1,122 @@
 <!DOCTYPE html>
 <html>
 <head>
-  <meta charset="utf-8">
-  <title>燎原管理系统</title>
-  <meta name="renderer" content="webkit">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-  <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" href="../../layuiadmin/style/login.css" media="all">
+    <meta charset="utf-8">
+    <title>人员能力评估后台管理系统</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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" href="../../layuiadmin/style/login.css" media="all">
 </head>
 <body>
 
-  <div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
+<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
 
     <div class="layadmin-user-login-main">
-      <div class="layadmin-user-login-box layadmin-user-login-header">
-        <h2>燎原管理系统</h2>
-      </div>
-      <form class="layui-form" action="" lay-filter="component-form-element">
-      <div class="layadmin-user-login-box layadmin-user-login-body layui-form">
-        <div class="layui-form-item">
-          <label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
-          <input type="text" name="username" id="LAY-user-login-username" placeholder="用户名" autocomplete="off" class="layui-input">
+        <div class="layadmin-user-login-box layadmin-user-login-header">
+            <font style="font-size: 25px">人员能力评估后台管理系统</font>
         </div>
-        <div class="layui-form-item">
-          <label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
-          <input type="password" name="password" id="LAY-user-login-password" autocomplete="off" placeholder="密码" class="layui-input">
-        </div>
-        <div class="layui-form-item">
-          <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="component-form-element">登 录</button>
-        </div>
-      </div>
-       </form>
+        <form class="layui-form" action="" lay-filter="component-form-element">
+            <div class="layadmin-user-login-box layadmin-user-login-body layui-form">
+                <div class="layui-form-item">
+                    <label class="layadmin-user-login-icon layui-icon layui-icon-username"
+                           for="LAY-user-login-username"></label>
+                    <input type="text" name="username" id="LAY-user-login-username" placeholder="用户名" autocomplete="off"
+                           class="layui-input">
+                </div>
+                <div class="layui-form-item">
+                    <label class="layadmin-user-login-icon layui-icon layui-icon-password"
+                           for="LAY-user-login-password"></label>
+                    <input type="password" name="password" id="LAY-user-login-password" autocomplete="off"
+                           placeholder="密码" class="layui-input">
+                </div>
+                <div class="layui-form-item">
+                    <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="component-form-element">登 录
+                    </button>
+                </div>
+            </div>
+        </form>
     </div>
-    
+
     <div class="layui-trans layadmin-user-login-footer">
-      <p>© 2020 燎原管理系统 <a href="http://www.zzliaoyuan.com/" target="_blank">郑州燎原计算机技术有限公司</a></p>
     </div>
-    
-  </div>
 
-  <script src="../../layuiadmin/layui/layui.js"></script>
-  <script>
-  layui.config({
-    base: '../../layuiadmin/' //静态资源所在路径
-  }).extend({
-    index: 'lib/index' //主入口模块
-  }).use(['index', 'user'], function(){
-    var $ = layui.$
-    ,setter = layui.setter
-    ,admin = layui.admin
-    ,form = layui.form
-    ,router = layui.router()
-    ,search = router.search;
+</div>
 
-    if (layui.data(setter.tableName)[setter.request.tokenName]) {
-      admin.req({
-        url: '/admin/token_refresh/'
-        ,data: {token: layui.data(setter.tableName)[setter.request.tokenName].substr(4)}
-        ,type: 'post'
-        ,done: function(res){
+<script src="../../layuiadmin/layui/layui.js"></script>
+<script>
+    layui.config({
+        base: '../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'user'], function () {
+        var $ = layui.$
+            , setter = layui.setter
+            , admin = layui.admin
+            , form = layui.form
+            , router = layui.router()
+            , search = router.search;
 
-          //请求成功后,写入 access_token
-          layui.data(setter.tableName, {
-            key: setter.request.tokenName
-            ,value: 'JWT ' + res.data.token
-          });
+        if (layui.data(setter.tableName)[setter.request.tokenName]) {
+            admin.req({
+                url: '/admin/token_refresh/'
+                , data: {token: layui.data(setter.tableName)[setter.request.tokenName].substr(4)}
+                , type: 'post'
+                , done: function (res) {
+                    //请求成功后,写入 access_token
+                    layui.data(setter.tableName, {
+                        key: setter.request.tokenName
+                        , value: 'JWT ' + res.data.token
+                    });
 
-          location.href = '../index.html'; //后台主页
+                    location.href = '../index.html'; //后台主页
+                }
+            });
         }
-      });
-    }
 
-    form.render();
+        form.render();
 
-    //提交
-    form.on('submit(component-form-element)', function(obj){
+        //提交
+        form.on('submit(component-form-element)', function (obj) {
 
-      //请求登入接口
-      admin.req({
-        url: '/admin/token/obtain/'
-        ,data: obj.field
-        ,type: 'post'
-        ,done: function(res){
+            //请求登入接口
+            admin.req({
+                url: '/admin/token/obtain/'
+                , data: obj.field
+                , type: 'post'
+                , done: function (res) {
 
-          //请求成功后,写入 access_token
-          layui.data(setter.tableName, {
-            key: setter.request.tokenName
-            ,value: 'JWT ' + res.data.token
-          });
-          layui.data(setter.tableName, {
-            key: setter.request.userId
-            ,value: res.data.user_id
-          });
-          layui.data(setter.tableName, {
-            key: 'name'
-            ,value: res.data.name ? res.data.name: res.data.username
-          });
+                    //请求成功后,写入 access_token
+                    layui.data(setter.tableName, {
+                        key: setter.request.tokenName
+                        , value: 'JWT ' + res.data.token
+                    });
+                    layui.data(setter.tableName, {
+                        key: setter.request.userId
+                        , value: res.data.user_id
+                    });
+                    layui.data(setter.tableName, {
+                        key: 'name'
+                        , value: res.data.name ? res.data.name : res.data.username
+                    });
 
-          //登入成功的提示与跳转
-          layer.msg('登入成功', {
-            offset: '15px'
-            ,icon: 1
-            ,time: 1000
-          }, function(){
-            location.href = '../index.html'; //后台主页
-          });
-        }
-      });
-      return false;
-      
+                    //登入成功的提示与跳转
+                    layer.msg('登入成功', {
+                        offset: '15px'
+                        , icon: 1
+                        , time: 1000
+                    }, function () {
+                        location.href = '../index.html'; //后台主页
+                    });
+                }
+            });
+            return false;
+
+        });
     });
-  });
-  </script>
+</script>
 </body>
-</html>
+</html>

+ 118 - 0
uis/admin/permission/index.html

@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>修改密码</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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">
+   <style type="text/css">
+        .input {
+            width: 70%;
+            border-radius: 2px;
+            height: 30px;
+            border-width: 1px;
+            border-color: lightgray;
+            border-style: solid;
+            background-color: white;
+            padding-left: 3px;
+            margin-top: 20px;
+        }
+    </style>
+</head>
+<body>
+
+  <div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+            <form class="layui-form" action="" lay-filter="component-form-element">
+              <div class="layui-row layui-col-space10 layui-form-item">
+                <div>
+                  <label><font color='red' size="4">*</font>商品提成比例:</label>
+                  <input type="text" name="package_percentage" lay-verify="required|numberGtZ" autocomplete="off" class="input">
+                  <label><font color='red' size="4" style="font-size: small;">佣金=商品优惠价*提成比例</font></label>
+                </div>
+                <div>
+                    <label ><font color='red' size="4">*</font>整车提成比例:</label>
+                    <input type="text" name="vehicle_percentage" lay-verify="required|numberGtZ" autocomplete="off" class="input">
+                    <label><font color='red' style="font-size: small;">佣金=车型定金*提成比例</font></label>
+                </div>
+              <div class="layui-form-item">
+                <div class="layui-input-block">
+                  <button class="layui-btn" lay-submit lay-filter="component-form-element">保存</button>
+                </div>
+              </div>
+              </div>
+            </form>
+          </div>
+        </div>
+      </div>
+
+
+  <script src="../../layuiadmin/layui/layui.js"></script>
+  <script>
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index' //主入口模块
+  }).use(['index', 'form', 'utils'], function(){
+    var $ = layui.$
+    ,admin = layui.admin
+    ,element = layui.element
+    ,form = layui.form;
+    form.render(null, 'component-form-element');
+    element.render('breadcrumb', 'breadcrumb');
+
+    admin.req({
+        url: '/tenant/config/',
+        done: function (res) {
+            var data = res.data;
+            var edit_data = {};
+            for(var i in data) {
+                if (data[i].property === 'package_percentage'){
+                    edit_data['package_percentage'] = data[i].value;
+                }else if (data[i].property === 'vehicle_percentage') {
+                    edit_data['vehicle_percentage'] = data[i].value;
+                }
+            }
+            form.val("component-form-element", edit_data);
+            form.render();
+        }
+    });
+
+    form.on('submit(component-form-element)', function(data){
+      var save_data = [];
+      if (data.field['package_percentage']){
+          save_data.push({key: 'package_percentage', value: data.field['package_percentage']})
+      }
+      if (data.field['vehicle_percentage']){
+          save_data.push({key: 'vehicle_percentage', value: data.field['vehicle_percentage']})
+      }
+      admin.req({
+        url: '/tenant/config/'
+        ,data: {data: JSON.stringify(save_data)}
+        ,type: 'post'
+        ,done: function(res){
+            layer.open({
+                 type: 1
+                ,content: '<div style="padding: 20px 100px;">保存成功</div>'
+                ,btn: '关闭'
+                ,btnAlign: 'c' //按钮居中
+                ,shade: 0 //不显示遮罩
+                ,yes: function(){
+                  layer.closeAll();
+                  //admin.exit();
+                }
+            });
+        }
+      });
+
+      return false;
+    });
+
+  });
+  </script>
+</body>
+</html>

二進制
uis/admin/right.png


+ 113 - 0
uis/admin/subject/chapter_edit.html

@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>添加章节</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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"/>
+</head>
+<body>
+
+  <div class="layui-fluid">
+    <div class="layui-row layui-col-space15">
+        <div class="layui-card">
+
+          <div class="layui-card-body" pad15>
+            <form class="layui-form" action="" lay-filter="component-form-element">
+              <div class="layui-row layui-col-space10 layui-form-item">
+                <div>
+                  <label class="layui-form-label"><font color='red' size="4">*</font>科目:</label>
+                  <div class="layui-input-block">
+                    <select id="id_subject" name="subject" lay-verify="required">
+                    </select>
+                  </div>
+              </div>
+                <div>
+                  <label class="layui-form-label"><font color='red' size="4">*</font>名称:</label>
+                  <div class="layui-input-block">
+                    <input type="text" name="name" lay-verify="required" placeholder="请输入名称" autocomplete="off" class="layui-input">
+                  </div>
+                </div>
+                <div>
+                  <label class="layui-form-label">备注:</label>
+                  <div class="layui-input-block">
+                    <input type="text" name="notes" placeholder="请输入备注"  autocomplete="off" class="layui-input">
+                  </div>
+                </div>
+                <button id="id_save" class="layui-btn" lay-submit lay-filter="component-form-element" style="display: none">保存</button>
+              </div>
+            </form>
+          </div>
+        </div>
+      </div>
+    </div>
+
+
+  <script src="../../layuiadmin/layui/layui.js"></script>
+  <script>
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index',
+    formSelects: 'formSelects-v4'
+  }).use(['index', 'form', 'utils', 'upload'], function(){
+    var $ = layui.$
+    ,admin = layui.admin
+    ,form = layui.form;
+    var id = layui.view.getParameterByName('id');
+
+     admin.req({
+        url: '/admin/subject/dict/',
+        done: function (res) {
+            var subject = res.data.subject;
+            var subject_node = $('#id_subject');
+            for (var i in subject) {
+                var pid = subject[i].id;
+                var value = subject[i].name;
+                subject_node.append("<option value='" + pid + "'>" + value + "</option>");
+            }
+            form.render();
+            loadData();
+        }
+    });
+     var loadData = function(){
+        if(id){
+            var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+            form.val("component-form-element", editdata);
+        }
+    };
+     form.render(null, 'component-form-element');
+
+    form.on('submit(component-form-element)', function(data){
+      //layer.msg(JSON.stringify(data.field));
+     if (id){
+          var url = '/admin/subject/chapter/'+id + '/';
+          var type = 'put';
+      }else{
+          url =  '/admin/subject/chapter/';
+          type = 'post';
+      }
+
+    admin.req({
+        url: url
+        ,type:type
+        ,data: data.field
+        ,done: function(res){
+            parent.layui.onSubmitChild(res.data);
+        }
+      });
+
+      return false;
+    });
+
+    parent.layui.submitChild = function () {
+      $("#id_save").click();
+    };
+  });
+  </script>
+</body>
+</html>

+ 259 - 0
uis/admin/subject/index.html

@@ -0,0 +1,259 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>科目章节设置</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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">
+    <style type="text/css">
+        .seach_items {float:right;margin-left: 10px;}
+    </style>
+
+</head>
+<body>
+
+  <div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+        <div class="layui-row layui-col-space15">
+          <div class="layui-col-md5">
+            <div class="LAY-btns" style="margin-bottom: 10px;">
+                <div class="layui-col-xs2">
+                    <button class="layui-btn" id="subject_add"><i class="layui-icon layui-icon-add-circle"></i>添加</button>
+                </div>
+                    <form class="layui-form" lay-filter="query-form-element1">
+                        <div class="seach_items">
+                            <button class="layui-btn" lay-submit lay-filter="query-form-element1"><i class="layui-icon layui-icon-search"></i>查询</button>
+                        </div>
+                        <div class="seach_items">
+                            <input type="text"  name="name" autocomplete="off" class="layui-input" placeholder="名称"/>
+                        </div>
+                    </form>
+                <div style="clear: both;"></div>
+            </div>
+            <table class="layui-hide" id="subject_datagrid" lay-filter="subject-operate"></table>
+
+            <script type="text/html" id="subject-operate-bar">
+                <div class="layui-btn-group">
+              <a class="layui-btn layui-btn-xs" lay-event="subject_edit">修改</a>
+              <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="subject_del">删除</a>
+                </div>
+            </script>
+          </div>
+
+          <div class="layui-col-md7">
+            <div class="LAY-btns" style="margin-bottom: 10px;">
+              <div class="layui-col-xs2">
+                    <button class="layui-btn" id="chapter_add"><i class="layui-icon layui-icon-add-circle"></i>添加</button>
+                </div>
+
+                    <form class="layui-form" lay-filter="query-form-element2">
+                        <div class="seach_items">
+                            <button class="layui-btn" lay-submit lay-filter="query-form-element2"><i class="layui-icon layui-icon-search"></i>查询</button>
+                        </div>
+                        <div class="seach_items">
+                            <input type="text"  name="name" autocomplete="off" class="layui-input" placeholder="章节名称"/>
+                        </div>
+                        <div class="seach_items">
+                            <input type="text"  name="subject_name" autocomplete="off" class="layui-input" placeholder="科目名称"/>
+                        </div>
+                    </form>
+                <div style="clear: both;"></div>
+                </div>
+
+            <table class="layui-hide" id="chapter_datagrid" lay-filter="chapter-operate"></table>
+
+            <script type="text/html" id="chapter-operate-bar">
+                <div class="layui-btn-group">
+              <a class="layui-btn layui-btn-xs" lay-event="chapter_edit">修改</a>
+              <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="chapter_del">删除</a>
+                </div>
+            </script>
+          </div>
+        </div>
+        </div>
+    </div>
+  </div>
+  <script src="../../layuiadmin/layui/layui.js?t=1"></script>
+  <script>
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index' //主入口模块
+  }).use(['index', 'table', 'form'], function(){
+    var $ = layui.$;
+    var table = layui.table
+        ,form = layui.form
+        ,admin = layui.admin;
+      //品牌
+    table.render({
+      elem: '#subject_datagrid'
+      ,url: '/admin/subject/'
+      ,cols: [[
+        {field:'name', title:'名称', width:150}
+        ,{field:'notes', title:'备注'}
+        ,{width:110, align:'center', title:'操作', fixed: 'right', toolbar: '#subject-operate-bar'}
+      ]]
+      ,page: true
+      ,height: 'full-108'
+      , done: function () {
+        layui.index.removeNoPermButtons()
+      }
+    });
+    //车系
+    table.render({
+      elem: '#chapter_datagrid'
+      ,url: '/admin/subject/chapter/'
+      ,cols: [[
+        {field:'name', title:'名称', width:150}
+        ,{field:'subject_name', title:'科目', width:150}
+        ,{field:'notes', title:'备注'}
+        ,{width:110, align:'center', title: '操作', fixed: 'right', toolbar: '#chapter-operate-bar'}
+      ]]
+      ,page: true
+      ,height: 'full-108'
+      , done: function () {
+        layui.index.removeNoPermButtons()
+      }
+    });
+
+    //监听品牌工具条
+    table.on('tool(subject-operate)', function(obj) {
+        var data = obj.data;
+        if (obj.event === 'subject_del') {
+            layer.confirm('确定要删除吗?', function (index) {
+                layer.close(index);
+                layui.admin.req({
+                    url: '/admin/subject/' + data.id + '/'
+                    ,type: 'delete'
+                    , done: function (res) {
+                        table.reload('subject_datagrid', {});
+                        layer.close(index);
+                    }
+                });
+            });
+        } else if (obj.event === 'subject_edit') {
+            table.editdata = data;
+            layer.open({
+                type: 2,
+                title: '修改',
+                shadeClose: false,
+                area: ['450px', '300px'],
+                btn:['保存','取消'],
+                yes: function (index, dom) {
+                    layui.onSubmitChild = function (data) {
+                        layer.close(index);
+                        table.reload('subject_datagrid', {});
+                        table.reload('chapter_datagrid', {});
+                      };
+                      layui.submitChild();
+                  },
+                  btn2: function(index, layero){
+                    layer.close(index);//关闭当前按钮
+                  },
+                content: 'subject_edit.html?id=' + data.id
+            });
+        }
+    });
+    //监听车系工具条
+    table.on('tool(chapter-operate)', function(obj){
+      var data = obj.data;
+        if(obj.event === 'chapter_del'){
+        layer.confirm('确定要删除吗?', function(index){
+          layui.admin.req({
+            url: '/admin/subject/chapter/'+data.id + '/'
+            ,type: 'delete'
+            ,done: function(res){
+              table.reload('chapter_datagrid',{});
+              layer.close(index);
+            }
+          });
+        });
+      } else if(obj.event === 'chapter_edit'){
+        table.editdata = data;
+        layer.open({
+          type: 2,
+          title: '修改',
+         area: ['500px', '500px'],
+          btn:['保存','取消'],
+          yes: function (index, dom) {
+            layui.onSubmitChild = function (data) {
+                layer.close(index);
+                table.reload('subject_datagrid', {});
+                table.reload('chapter_datagrid', {});
+              };
+              layui.submitChild();
+          },
+          btn2: function(index, layero){
+            layer.close(index);//关闭当前按钮
+          },
+          content: 'chapter_edit.html?id='+data.id
+        });
+      }
+    });
+    form.on('submit(query-form-element1)', function(data){
+      table.reload('subject_datagrid', {
+          where: data.field
+          ,page:{curr:1}
+      });
+      layer.closeAll();
+      return false
+    });
+    form.on('submit(query-form-element2)', function(data){
+      table.reload('chapter_datagrid', {
+          where: data.field
+          ,page:{curr:1}
+      });
+      layer.closeAll();
+      return false
+    });
+    $('#subject_add').on('click', function(){
+        layer.open({
+          type: 2,
+          title: '添加',
+          area: ['450px', '300px'],
+          btn:['保存','取消'],
+          yes: function (index, dom) {
+            layui.onSubmitChild = function (data) {
+                layer.close(index);
+                table.reload('subject_datagrid', {});
+                table.reload('chapter_datagrid', {});
+              };
+              layui.submitChild();
+          },
+          btn2: function(index, layero){
+            layer.close(index);//关闭当前按钮
+          },
+          content: 'subject_edit.html'
+        });
+    });
+    $('#chapter_add').on('click', function(){
+        layer.open({
+          type: 2,
+          title: '添加',
+          area: ['500px', '500px'],
+          btn:['保存','取消'],
+          yes: function (index, dom) {
+            layui.onSubmitChild = function (data) {
+                layer.close(index);
+                table.reload('subject_datagrid', {});
+                table.reload('chapter_datagrid', {});
+              };
+              layui.submitChild();
+          },
+          btn2: function(index, layero){
+            layer.close(index);//关闭当前按钮
+          },
+          content: 'chapter_edit.html'
+        });
+    });
+});
+
+
+
+  </script>
+</body>
+</html>

+ 91 - 0
uis/admin/subject/subject_edit.html

@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>添加科目</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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"/>
+</head>
+<body>
+
+  <div class="layui-fluid">
+    <div class="layui-row layui-col-space15">
+      <div class="layui-col-md6">
+        <div class="layui-card">
+
+          <div class="layui-card-body">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+              <div class="layui-row layui-col-space10 layui-form-item">
+
+                <div class="layui-col-lg6">
+                  <label class="layui-form-label"><font color='red' size="4">*</font>名称:</label>
+                  <div class="layui-input-block">
+                    <input type="text" name="name" lay-verify="required" placeholder="请输入名称" autocomplete="off" class="layui-input">
+                  </div>
+                </div>
+                 <div class="layui-col-lg6">
+                  <label class="layui-form-label">备注:</label>
+                  <div class="layui-input-block">
+                    <input type="text" name="notes" placeholder="请输入备注"  autocomplete="off" class="layui-input">
+                  </div>
+                </div>
+                <button id="id_save" class="layui-btn" lay-submit lay-filter="component-form-element" style="display: none">保存</button>
+              </div>
+            </form>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+
+  <script src="../../layuiadmin/layui/layui.js"></script>
+  <script>
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index',
+    formSelects: 'formSelects-v4'
+  }).use(['index', 'form', 'utils'], function(){
+    var $ = layui.$
+    ,admin = layui.admin
+    ,form = layui.form;
+    var id = layui.view.getParameterByName('id');
+
+    if(id){
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+        form.val("component-form-element", editdata);
+    }
+    form.on('submit(component-form-element)', function(data){
+
+      //layer.msg(JSON.stringify(data.field));
+        if (id){
+            var url =  '/admin/subject/' + id + '/';
+            var type = 'put';
+        }else{
+            url =  '/admin/subject/';
+            type = 'post'
+        }
+         admin.req({
+        url: url
+        ,data: data.field
+        ,type: type
+        ,done: function(res){
+            parent.layui.onSubmitChild(res.data);
+        }
+      });
+
+      return false;
+    });
+
+    parent.layui.submitChild = function () {
+      $("#id_save").click();
+    };
+  });
+  </script>
+</body>
+</html>

+ 166 - 0
uis/admin/user/edit.html

@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>账户管理</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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>
+
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+        <div class="layui-row layui-col-space15">
+          <div class="layui-col-md12">
+            <form class="layui-form" action="" lay-filter="component-form-element">
+              <div class="layui-row layui-col-space10 layui-form-item">
+
+                  <div class="layui-col-lg6">
+                  <label class="layui-form-label"><font color='red' size="4">*</font>类型:</label>
+                  <div class="layui-input-block">
+                    <select  name="type" id="id_type">
+                        <option value="1">管理员</option>
+                        <option value="2">员工</option>
+                    </select>
+                  </div>
+                </div>
+
+                  <div class="layui-col-lg6">
+                  <label class="layui-form-label"><font color='red' size="4">*</font>姓名:</label>
+                  <div class="layui-input-block">
+                    <input type="text" name="name" lay-verify="required" placeholder="请输入姓名" autocomplete="off" class="layui-input">
+                  </div>
+                </div>
+
+                  <div class="layui-col-lg6">
+                  <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>
+                </div>
+
+                  <div class="layui-col-lg6">
+                    <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>
+                  </div>
+
+                <div class="layui-col-lg6">
+                    <label class="layui-form-label"><font color='red' size="4">*</font>所属部门:</label>
+                    <div class="layui-input-block" id="department_selecter">
+                      <select name="department" xm-select="selectDepartment" xm-select-radio lay-verify="required">
+                      </select>
+                    </div>
+                  </div>
+
+                <div class="layui-col-lg6">
+                  <label class="layui-form-label">是否在职:</label>
+                  <div class="layui-input-block">
+                    <input type="checkbox" name="is_active" lay-skin="switch" lay-text="是|否" checked="" value="1">
+                  </div>
+                </div>
+
+                <button class="layui-btn" id="submit_btn" lay-submit lay-filter="component-form-element" style="display: none">保存</button>
+
+              </div>
+            </form>
+          </div>
+        </div>
+        </div>
+    </div>
+</div>
+
+  <script src="../../layuiadmin/layui/layui.js"></script>
+  <script>
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index',
+    utils:'utils',
+    formSelects: 'formSelects-v4'
+  }).use(['index','utils', 'form', 'formSelects'], function(){
+    var $ = layui.$
+    ,admin = layui.admin
+    ,element = layui.element
+    ,form = layui.form
+    ,formSelects = layui.formSelects
+    ,dep_formSelects = layui.formSelects;
+
+    var editdata = null;
+    var department = '';
+    var id = layui.view.getParameterByName('id');
+
+    form.render(null, 'component-form-element');
+    element.render('breadcrumb', 'breadcrumb');
+
+    if(id){
+      $('#id_type').attr('disabled', 'disabled');
+      department = parent.layui.table.editdata.department;
+      editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+      editdata.password = '';
+      var url = '/admin/user/' + id + '/';
+      var type = 'put';
+      if (editdata.is_active == 1) {
+        editdata.status = true;
+      } else {
+        editdata.status = false;
+      }
+    }else{
+        url = '/admin/user/';
+        type = 'post';
+    }
+
+    form.val("component-form-element", {'password':'1111'});
+    if(editdata){
+        form.val("component-form-element", editdata);
+    }
+
+    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]);
+        }
+    });
+
+    form.on('submit(component-form-element)', function(data){
+      var submitData = data.field;
+
+      admin.req({
+        url: url
+            ,type: type
+        ,data: submitData
+        ,done: function(res){
+            parent.layui.onSubmitChild(res.data);
+        }
+      });
+
+      return false;
+    });
+    parent.layui.submitChild = function () {
+      $("#submit_btn").click();
+    };
+
+  });
+  </script>
+</body>
+</html>

+ 186 - 0
uis/admin/user/index.html

@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>人员管理</title>
+  <meta name="renderer" content="webkit">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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">
+</head>
+<body>
+
+  <div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body" pad15>
+        <div class="layui-row layui-col-space15">
+          <div class="layui-col-md12">
+
+            <div class="LAY-btns" style="margin-bottom: 10px;">
+              <button class="layui-btn layui-btn-sm" id="btn_add"><i class="layui-icon layui-icon-add-circle"></i>添加</button>
+              <button class="layui-btn layui-btn-sm" id="btn_query"><i class="layui-icon layui-icon-search"></i>查询</button>
+            </div>
+
+            <table class="layui-hide" id="datagrid" lay-filter="datagrid-operate"></table>
+
+            <script type="text/html" id="datagrid-operate-bar">
+                <div class="layui-btn-group">
+                  <a class="layui-btn layui-btn-xs" lay-event="edit">修改</a>
+                </div>
+            </script>
+          </div>
+        </div>
+        </div>
+    </div>
+  </div>
+
+  <div id="dlg_query" style="display: none">
+    <div class="layui-card-body" pad15>
+        <form class="layui-form" lay-filter="query-form-element">
+          <div class="layui-row layui-col-space10 layui-form-item">
+              <div class="layui-col-xs12 layui-col-sm12">
+                <label class="layui-form-label">在职:</label>
+                <div class="layui-input-block">
+                    <select  name="is_active">
+                        <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">
+                    <select  name="type">
+                        <option value="" selected></option>
+                        <option value="1">管理员</option>
+                        <option value="2">员工</option>
+                    </select>
+                </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="username" autocomplete="off" class="layui-input">
+                </div>
+            </div>
+
+
+          </div>
+          <div class="layui-form-item" style="display: none">
+                <button id="query_search" class="layui-btn" lay-submit lay-filter="query-form-element">查询</button>
+          </div>
+        </form>
+    </div>
+   </div>
+
+  <script src="../../layuiadmin/layui/layui.js?t=1"></script>
+  <script>
+  var _params = '';
+  layui.config({
+    base: '../../../layuiadmin/' //静态资源所在路径
+  }).extend({
+    index: 'lib/index' //主入口模块
+  }).use(['index', 'table'], function(){
+    var $ = layui.$
+    ,form = layui.form;
+    var table = layui.table;
+
+    table.render({
+      elem: '#datagrid'
+      ,url: '/admin/user/'
+      ,cols: [[
+        {field:'name', title:'姓名',width: 100}
+        ,{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'}
+      ]]
+      ,page: true
+      ,height: 'full-104'
+      ,done: function () {
+        //layui.index.removeNoPermButtons();
+        layer.closeAll('loading');
+      }
+    });
+
+    //监听工具条
+    table.on('tool(datagrid-operate)', function(obj){
+      var data = obj.data;
+        if(obj.event === 'edit'){
+        table.editdata = data;
+        layer.open({
+          type: 2,
+          title: '修改',
+          shadeClose: false,
+          area: ['550px', '85%'],
+          btn: ['保存', '取消'],
+          yes: function(index, dom){
+             layui.onSubmitChild = function (data) {
+              layer.close(index);
+              table.reload('datagrid',{});
+            };
+            layui.submitChild();
+          },
+          btn2: function(index, layero){
+            layer.close(index);//关闭当前按钮
+          },
+          content: 'edit.html?id='+data.id
+        });
+      }
+    });
+
+    $('#btn_add').on('click', function(){
+        layer.open({
+          type: 2,
+          title: '添加',
+          shadeClose: false,
+          area: ['550px', '85%'],
+          btn: ['保存', '取消'],
+          yes: function(index, dom){
+             layui.onSubmitChild = function (data) {
+              layer.close(index);
+              table.reload('datagrid',{});
+            };
+            layui.submitChild();
+          },
+          btn2: function(index, layero){
+            layer.close(index);//关闭当前按钮
+          },
+          content: 'edit.html'
+        });
+    });
+
+    $('#btn_query').on('click', function(){
+        layer.open({
+          type: 1,
+          shadeClose: true,
+          area: ['500px', '370px'],
+          title: '查询',
+          btn: ['查询'],
+          yes: function(index, dom){
+             $('#query_search').click();
+          },
+          content: $('#dlg_query')
+        });
+    });
+
+    form.on('submit(query-form-element)', function(data){
+      //layer.msg(JSON.stringify(data.field));
+      _params = data.field;
+      table.reload('datagrid', {
+          where: data.field,
+          page:{curr:1}
+      });
+      layer.closeAll();
+      return false;
+    });
+
+  });
+  </script>
+</body>
+</html>
+

二進制
uis/admin/wrong.png


+ 1 - 0
uis/dist/css/app.3c9aeb10.css

@@ -0,0 +1 @@
+body{margin:0;font-family:Arial Negreta,Arial Normal,Arial}@font-face{font-family:iconfont;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")}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-zhanghao:before{content:"\e68d"}.icon-mima:before{content:"\e767"}.icon-kaoshijilu:before{content:"\e70f"}.icon-xiti:before{content:"\e62a"}.icon-cuotiji:before{content:"\e613"}.icon-monikaoshi:before{content:"\e67d"}.icon-zhengshikaoshi:before{content:"\e61f"}.icon-shuben:before{content:"\e612"}.icon-about:before{content:"\e610"}.icon-gantantishi:before{content:"\e64f"}

+ 1 - 0
uis/dist/css/chunk-0669d765.e3f50608.css

@@ -0,0 +1 @@
+.login[data-v-e088c436]{height:100vh;width:100vw;background-color:#3867b7;display:flex;align-items:center;justify-content:space-around}.login .card[data-v-e088c436]{width:350px;height:420px}.login .card .el-card[data-v-e088c436]{height:100%}.login .card .el-card .icon[data-v-e088c436]{margin:20px 0;text-align:center}.login .card .el-card .icon p[data-v-e088c436]{margin:0;font-family:微软雅黑 Bold,微软雅黑 Regular,微软雅黑;font-weight:700;font-style:normal;font-size:24px;color:#3867b7;line-height:28px}.login .card .el-card .form[data-v-e088c436]{margin-top:20px}.login .card .el-card .form .el-form-item[data-v-e088c436]{position:relative}.login .card .el-card .form .el-form-item i[data-v-e088c436]{display:inline-block;border:1px solid #dcdfe6;width:38px;text-align:center;line-height:38px;box-sizing:border-box;border-radius:4px}.login .card .el-card .form .el-form-item .el-button[data-v-e088c436]{width:100%}

+ 1 - 0
uis/dist/css/chunk-09e5b99e.ad48c20d.css

@@ -0,0 +1 @@
+.radio .item[data-v-1a553577]{font-size:12px;margin:10px 0;cursor:pointer}.radio .item>span[data-v-1a553577]:first-child{background-color:#eee;border-radius:50%;width:14px;line-height:14px;text-align:center}.radio .item span[data-v-1a553577]{display:inline-block;padding:5px}.radio .foucs[data-v-1a553577]{background-color:#67c23a!important;color:#fff}.radio .foucss[data-v-1a553577]{background-color:#f03!important;color:#fff}

+ 1 - 0
uis/dist/css/chunk-0b9a8692.eb49efda.css

@@ -0,0 +1 @@
+.radio[data-v-2a8f0442]{display:block}.radio .item[data-v-2a8f0442]{display:block;margin:10px 0;font-size:12px;cursor:pointer}.radio .item>span[data-v-2a8f0442]:first-child{background-color:#eee;border-radius:50%;width:14px;line-height:14px;text-align:center}.radio .item span[data-v-2a8f0442]{display:inline-block;padding:5px}.radio .foucs[data-v-2a8f0442]{background-color:#67c23a!important;color:#fff}.radio .foucss[data-v-2a8f0442]{background-color:#f03!important;color:#fff}

Some files were not shown because too many files changed in this diff