jiaweiqi 3 年之前
父节点
当前提交
56580aa741
共有 100 个文件被更改,包括 1014 次插入0 次删除
  1. 80 0
      apps/commodity/serializers.py
  2. 16 0
      apps/commodity/urls.py
  3. 172 0
      apps/commodity/views.py
  4. 0 0
      apps/config/__init__.py
  5. 12 0
      apps/config/filters.py
  6. 0 0
      apps/config/migrations/__init__.py
  7. 40 0
      apps/config/models.py
  8. 24 0
      apps/config/serializers.py
  9. 14 0
      apps/config/urls.py
  10. 51 0
      apps/config/views.py
  11. 82 0
      apps/customer/serializers.py
  12. 14 0
      apps/customer/urls.py
  13. 69 0
      apps/customer/views.py
  14. 89 0
      apps/employee/serializers.py
  15. 15 0
      apps/employee/urls.py
  16. 99 0
      apps/employee/views.py
  17. 36 0
      apps/option/serializers.py
  18. 13 0
      apps/option/urls.py
  19. 52 0
      apps/option/views.py
  20. 10 0
      requirements
  21. 95 0
      uis/layuiadmin/config.js
  22. 1 0
      uis/layuiadmin/layui/css/layui.css
  23. 1 0
      uis/layuiadmin/layui/css/layui.mobile.css
  24. 2 0
      uis/layuiadmin/layui/css/modules/code.css
  25. 1 0
      uis/layuiadmin/layui/css/modules/laydate/default/laydate.css
  26. 二进制
      uis/layuiadmin/layui/css/modules/layer/default/icon-ext.png
  27. 二进制
      uis/layuiadmin/layui/css/modules/layer/default/icon.png
  28. 1 0
      uis/layuiadmin/layui/css/modules/layer/default/layer.css
  29. 二进制
      uis/layuiadmin/layui/css/modules/layer/default/loading-0.gif
  30. 二进制
      uis/layuiadmin/layui/css/modules/layer/default/loading-1.gif
  31. 二进制
      uis/layuiadmin/layui/css/modules/layer/default/loading-2.gif
  32. 二进制
      uis/layuiadmin/layui/font/iconfont.eot
  33. 25 0
      uis/layuiadmin/layui/font/iconfont.svg
  34. 二进制
      uis/layuiadmin/layui/font/iconfont.ttf
  35. 二进制
      uis/layuiadmin/layui/font/iconfont.woff
  36. 二进制
      uis/layuiadmin/layui/images/face/0.gif
  37. 二进制
      uis/layuiadmin/layui/images/face/1.gif
  38. 二进制
      uis/layuiadmin/layui/images/face/10.gif
  39. 二进制
      uis/layuiadmin/layui/images/face/11.gif
  40. 二进制
      uis/layuiadmin/layui/images/face/12.gif
  41. 二进制
      uis/layuiadmin/layui/images/face/13.gif
  42. 二进制
      uis/layuiadmin/layui/images/face/14.gif
  43. 二进制
      uis/layuiadmin/layui/images/face/15.gif
  44. 二进制
      uis/layuiadmin/layui/images/face/16.gif
  45. 二进制
      uis/layuiadmin/layui/images/face/17.gif
  46. 二进制
      uis/layuiadmin/layui/images/face/18.gif
  47. 二进制
      uis/layuiadmin/layui/images/face/19.gif
  48. 二进制
      uis/layuiadmin/layui/images/face/2.gif
  49. 二进制
      uis/layuiadmin/layui/images/face/20.gif
  50. 二进制
      uis/layuiadmin/layui/images/face/21.gif
  51. 二进制
      uis/layuiadmin/layui/images/face/22.gif
  52. 二进制
      uis/layuiadmin/layui/images/face/23.gif
  53. 二进制
      uis/layuiadmin/layui/images/face/24.gif
  54. 二进制
      uis/layuiadmin/layui/images/face/25.gif
  55. 二进制
      uis/layuiadmin/layui/images/face/26.gif
  56. 二进制
      uis/layuiadmin/layui/images/face/27.gif
  57. 二进制
      uis/layuiadmin/layui/images/face/28.gif
  58. 二进制
      uis/layuiadmin/layui/images/face/29.gif
  59. 二进制
      uis/layuiadmin/layui/images/face/3.gif
  60. 二进制
      uis/layuiadmin/layui/images/face/30.gif
  61. 二进制
      uis/layuiadmin/layui/images/face/31.gif
  62. 二进制
      uis/layuiadmin/layui/images/face/32.gif
  63. 二进制
      uis/layuiadmin/layui/images/face/33.gif
  64. 二进制
      uis/layuiadmin/layui/images/face/34.gif
  65. 二进制
      uis/layuiadmin/layui/images/face/35.gif
  66. 二进制
      uis/layuiadmin/layui/images/face/36.gif
  67. 二进制
      uis/layuiadmin/layui/images/face/37.gif
  68. 二进制
      uis/layuiadmin/layui/images/face/38.gif
  69. 二进制
      uis/layuiadmin/layui/images/face/39.gif
  70. 二进制
      uis/layuiadmin/layui/images/face/4.gif
  71. 二进制
      uis/layuiadmin/layui/images/face/40.gif
  72. 二进制
      uis/layuiadmin/layui/images/face/41.gif
  73. 二进制
      uis/layuiadmin/layui/images/face/42.gif
  74. 二进制
      uis/layuiadmin/layui/images/face/43.gif
  75. 二进制
      uis/layuiadmin/layui/images/face/44.gif
  76. 二进制
      uis/layuiadmin/layui/images/face/45.gif
  77. 二进制
      uis/layuiadmin/layui/images/face/46.gif
  78. 二进制
      uis/layuiadmin/layui/images/face/47.gif
  79. 二进制
      uis/layuiadmin/layui/images/face/48.gif
  80. 二进制
      uis/layuiadmin/layui/images/face/49.gif
  81. 二进制
      uis/layuiadmin/layui/images/face/5.gif
  82. 二进制
      uis/layuiadmin/layui/images/face/50.gif
  83. 二进制
      uis/layuiadmin/layui/images/face/51.gif
  84. 二进制
      uis/layuiadmin/layui/images/face/52.gif
  85. 二进制
      uis/layuiadmin/layui/images/face/53.gif
  86. 二进制
      uis/layuiadmin/layui/images/face/54.gif
  87. 二进制
      uis/layuiadmin/layui/images/face/55.gif
  88. 二进制
      uis/layuiadmin/layui/images/face/56.gif
  89. 二进制
      uis/layuiadmin/layui/images/face/57.gif
  90. 二进制
      uis/layuiadmin/layui/images/face/58.gif
  91. 二进制
      uis/layuiadmin/layui/images/face/59.gif
  92. 二进制
      uis/layuiadmin/layui/images/face/6.gif
  93. 二进制
      uis/layuiadmin/layui/images/face/60.gif
  94. 二进制
      uis/layuiadmin/layui/images/face/61.gif
  95. 二进制
      uis/layuiadmin/layui/images/face/62.gif
  96. 二进制
      uis/layuiadmin/layui/images/face/63.gif
  97. 二进制
      uis/layuiadmin/layui/images/face/64.gif
  98. 二进制
      uis/layuiadmin/layui/images/face/65.gif
  99. 二进制
      uis/layuiadmin/layui/images/face/66.gif
  100. 二进制
      uis/layuiadmin/layui/images/face/67.gif

+ 80 - 0
apps/commodity/serializers.py

@@ -0,0 +1,80 @@
+# coding=utf-8
+
+import json
+
+from django.conf import settings
+from rest_framework import serializers
+from django.db.models import Q
+
+from apps.commodity.models import Commodity, CommodityImage
+from apps.images.models import Images
+from apps.base import Formater
+
+from utils.exceptions import CustomError
+
+
+class CommoditySerializer(serializers.ModelSerializer):
+    category_text = serializers.CharField(source='category.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.employee.name', read_only=True)
+    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
+    price = serializers.SerializerMethodField()
+    vip_price = serializers.SerializerMethodField()
+    point_price = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Commodity
+        fields = '__all__'
+
+    def get_price(self, obj):
+        return Formater.formatPriceShow(obj.price)
+
+    def get_vip_price(self, obj):
+        return Formater.formatPriceShow(obj.vip_price)
+
+    def get_point_price(self, obj):
+        return Formater.formatPriceShow(obj.point_price)
+
+    def validate(self, attrs):
+        if 'category' in attrs and attrs['category']:
+            if not attrs['category'].enable:
+                raise CustomError(u'该商品类别已被禁用,请刷新后重试!')
+            if not attrs['category'].delete:
+                raise CustomError(u'该商品类别已被删除,请刷新后重试!')
+        if 'price' in self.initial_data:
+            attrs['price'] = Formater.formatPrice(self.initial_data['price'])
+        if 'vip_price' in self.initial_data:
+            attrs['vip_price'] = Formater.formatPrice(self.initial_data['vip_price'])
+        if 'point_price' in self.initial_data:
+            attrs['point_price'] = Formater.formatPrice(self.initial_data['point_price'])
+
+        if attrs['type'] == Commodity.CASH:
+            attrs['point_price'] = 0
+        elif attrs['type'] == Commodity.POINT:
+            attrs['price'] = 0
+            attrs['vip_price'] = 0
+        return attrs
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        instance = super(CommoditySerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        if instance.delete:
+            raise CustomError(u'该商品已删除,禁止操作!')
+        instance = super(CommoditySerializer, self).update(instance, validated_data)
+        return instance
+
+
+class CommodityImageSerializer(serializers.ModelSerializer):
+    img_url = serializers.SerializerMethodField()
+    image_name = serializers.CharField(source='img.name', read_only=True)
+
+    def get_img_url(self, obj):
+        return '%s%s' % (settings.MEDIA_URL, obj.img.picture)
+
+    class Meta:
+        model = CommodityImage
+        fields = ('img_url', 'image_name', 'id',)

+ 16 - 0
apps/commodity/urls.py

@@ -0,0 +1,16 @@
+# coding=utf-8
+
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    # url(r'search/$', PackageSearch.as_view()),
+    url(r'dict/$', CommodityDict.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'images', CommodityImageViewSet)
+router.register(r'', CommodityViewSet)
+urlpatterns += router.urls

+ 172 - 0
apps/commodity/views.py

@@ -0,0 +1,172 @@
+# coding=utf-8
+
+from django.db import transaction
+from rest_framework.decorators import action
+from rest_framework.views import APIView
+from utils import response_ok
+from utils.permission import IsEmployee
+from utils.exceptions import CustomError
+from utils.custom_modelviewset import CustomModelViewSet
+from apps.log.models import BizLog
+from apps.images.models import Images
+from apps.commodity.models import Commodity, CommodityImage
+from apps.commodity.filters import CommodityFilter, CommodityImageFilter
+from .serializers import CommoditySerializer, CommodityImageSerializer
+from apps.option.models import Option
+from apps.base import Formater
+
+
+class CommodityDict(APIView):
+    permission_classes = [IsEmployee, ]
+
+    def get(self, request):
+        rows = Option.objects.filter(type=Option.COMMODITY_CATEGORY, enable=True, delete=False).order_by('sort').values('id', 'name')
+        data = [{'value': row['id'], 'name': row['name']} for row in rows]
+        ret = {
+            'category': data
+        }
+        return response_ok(ret)
+
+
+class CommodityViewSet(CustomModelViewSet):
+    permission_classes = [IsEmployee, ]
+    queryset = Commodity.objects.filter(delete=False)
+    serializer_class = CommoditySerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter()
+        f = CommodityFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(CommodityViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'添加商品[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(CommodityViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.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()
+            BizLog.objects.addnew(self.request.user, BizLog.DELETE, u'删除商品[%s],id=%d' % (instance.name, instance.id))
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def update_image(self, request, pk):
+        file = request.FILES.get('image', None)
+        user = request.user
+        instance = self.get_object()
+        if instance.delete:
+            raise CustomError(u'该商品已删除,禁止操作!')
+
+        with transaction.atomic():
+            serializer = self.get_serializer(instance, data=request.data)
+            serializer.is_valid(raise_exception=True)
+            self.perform_update(serializer)
+
+            # tenant_log(user.employee, BizLog.UPDATE, u'修改商品[%s],id=%d' % (instance.name, instance.id), request.data)
+
+            if file:
+                old_thumbnail = instance.thumbnail
+                thumbnail = Images.objects.employee_addnew(user.employee, Images.PACKAGE_THUMBNAIL_FILE, file)
+                instance.thumbnail = thumbnail
+                instance.save()
+                if old_thumbnail:
+                    old_thumbnail.del_images()
+
+        return response_ok()
+    #
+    # @action(methods=['post'], detail=True)
+    # def upload_playbill(self, request, pk):
+    #     if not self.request.user.has_perm('package.edit_package'):
+    #         raise CustomError(u"您没有[商品管理-修改]权限,无法执行该操作,请联系管理员分配权限!")
+    #     file = request.FILES.get('image', None)
+    #     user = request.user
+    #     instance = self.get_object()
+    #     if instance.delete:
+    #         raise CustomError(u'该商品已删除,禁止操作!')
+    #     if instance.tenant != user.employee.tenant:
+    #         raise CustomError(u'禁止跨租户操作!')
+    #     if not file:
+    #         raise CustomError(u'未发现上传文件!')
+    #
+    #     with transaction.atomic():
+    #         # tenant_log(user.employee, BizLog.UPDATE, u'商品[%s]上传海报,id=%d' % (instance.name, instance.id))
+    #         old_playbill = instance.playbill
+    #         playbill = Images.objects.employee_addnew(user.employee, Images.PACKAGE_PLAYBILL_FILE, file)
+    #         instance.playbill = playbill
+    #         instance.save()
+    #         if old_playbill:
+    #             old_playbill.del_images()
+    #
+    #     return response_ok()
+    #
+    # @action(methods=['post'], detail=True)
+    # def upload_images(self, request, pk):
+    #     if not self.request.user.has_perm('package.edit_package'):
+    #         raise CustomError(u"您没有[商品管理-修改]权限,无法执行该操作,请联系管理员分配权限!")
+    #     instance = self.get_object()
+    #     images = request.FILES.get('images', None)
+    #     user = request.user
+    #
+    #     if instance.delete:
+    #         raise CustomError(u'该商品已删除,禁止操作!')
+    #     if instance.tenant != user.employee.tenant:
+    #         raise CustomError(u'禁止跨租户操作!')
+    #     if not images:
+    #         raise CustomError(u'未找到上传文件!')
+    #
+    #     with transaction.atomic():
+    #         image = Images.objects.employee_addnew(user.employee, Images.PACKAGE_FILE, images)
+    #         CommodityImage.objects.create(main=instance, img=image)
+    #
+    #         # tenant_log(user.employee, BizLog.INSERT, u'商品[%s]上传图片儿,id=%d' % (instance.name, instance.id))
+    #
+    #     return response_ok()
+
+
+class CommodityImageViewSet(CustomModelViewSet):
+    permission_classes = [IsEmployee, ]
+    queryset = CommodityImage.objects.filter()
+    serializer_class = CommodityImageSerializer
+
+    def filter_queryset(self, queryset):
+        f = CommodityImageFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def list(self, request, *args, **kwargs):
+        queryset = self.filter_queryset(self.get_queryset())
+        serializer = self.get_serializer(queryset, many=True)
+        return response_ok(serializer.data)
+
+    def destroy(self, request, *args, **kwargs):
+        with transaction.atomic():
+            instance = self.get_object()
+            if instance.main.tenant != request.user.employee.tenant:
+                raise CustomError(u'禁止跨租户操作!')
+
+            # tenant_log(self.request.user.employee, BizLog.DELETE, u'删除商品[%s]图片,id=%d' % (instance.main.name, instance.main.id))
+            img = instance.img
+            instance.delete()
+            img.del_images()
+        return response_ok()
+
+
+# class PackageSearch(APIView):
+#     permission_classes = [IsEmployee, ]
+#
+#     def get(self, request):
+#         keyword = request.GET.get('keyword')
+#         tenant = request.user.employee.tenant
+#         rows = Package.objects.filter(delete=False, enabled=True, tenant=tenant)
+#         if keyword:
+#             rows = rows.filter(name__icontains=keyword)
+#         serializer = PackageSerializer(rows, many=True)
+#         return response_ok(serializer.data)

+ 0 - 0
apps/config/__init__.py


+ 12 - 0
apps/config/filters.py

@@ -0,0 +1,12 @@
+# coding=utf-8
+
+import django_filters
+
+from apps.config.models import Config
+
+
+class ConfigFilter(django_filters.FilterSet):
+
+    class Meta:
+        model = Config
+        fields = '__all__'

+ 0 - 0
apps/config/migrations/__init__.py


+ 40 - 0
apps/config/models.py

@@ -0,0 +1,40 @@
+# coding=utf-8
+
+from django.db import models
+
+
+class Config(models.Model):
+    # 购买商品赠送积分比例
+    KEY_POINT_RULE = "point_rule"
+    # 推荐用户首次购买返利  1、2、3级返现金比例  4、5级返积分比例
+    KEY_FIRST_LEVEL1 = "first_lv1"
+    KEY_FIRST_LEVEL2 = "first_lv2"
+    KEY_FIRST_LEVEL3 = "first_lv3"
+    KEY_FIRST_LEVEL4 = "first_lv4"
+    KEY_FIRST_LEVEL5 = "first_lv5"
+    # 推荐用户再次购买返利  1、2、3级返现金比例  4、5级返积分比例
+    KEY_AGAIN_LEVEL1 = "again_lv1"
+    KEY_AGAIN_LEVEL2 = "again_lv2"
+    KEY_AGAIN_LEVEL3 = "again_lv3"
+    KEY_AGAIN_LEVEL4 = "again_lv4"
+    KEY_AGAIN_LEVEL5 = "again_lv5"
+
+    property = models.CharField(max_length=100, verbose_name=u'属性')
+    value = models.TextField(verbose_name=u'值')
+
+    class Meta:
+        db_table = "system_config"
+        verbose_name = u"综合设置"
+        index_together = (
+            'property',
+        )
+        default_permissions = ()
+        permissions = []
+
+    @staticmethod
+    def getPackagePercentage(tenant_id):
+        try:
+            row = Config.objects.get(property=Config.KEY_POINT_RULE)
+            return float(row.value)
+        except:
+            return 0

+ 24 - 0
apps/config/serializers.py

@@ -0,0 +1,24 @@
+# coding=utf-8
+
+from rest_framework import serializers
+
+from django.conf import settings
+
+from utils.exceptions import CustomError
+
+from apps.config.models import Config
+
+
+class ConfigSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Config
+        fields = '__all__'
+
+    def create(self, validated_data):
+        instance = super(ConfigSerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        instance = super(ConfigSerializer, self).update(instance, validated_data)
+        return instance

+ 14 - 0
apps/config/urls.py

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

+ 51 - 0
apps/config/views.py

@@ -0,0 +1,51 @@
+# coding=utf-8
+
+import json
+
+from django.db import transaction
+
+from utils.custom_modelviewset import CustomModelViewSet
+from utils import response_ok, response_error
+from utils.exceptions import CustomError
+from utils.permission import IsEmployee
+
+from apps.config.models import Config
+from apps.config.serializers import ConfigSerializer
+from apps.config.filters import ConfigFilter
+from apps.log.models import BizLog
+
+
+class ConfigViewSet(CustomModelViewSet):
+    permission_classes = [IsEmployee, ]
+    queryset = Config.objects.filter()
+    serializer_class = ConfigSerializer
+
+    def list(self, request, *args, **kwargs):
+        queryset = self.filter_queryset(self.get_queryset())
+        serializer = self.get_serializer(queryset, many=True)
+        return response_ok(serializer.data)
+
+    def create(self, request, *args,**kwargs):
+        data = json.loads(request.POST.get('data'))
+        keys = [
+            'point_rule',
+            'first_lv1', 'first_lv2', 'first_lv3', 'first_lv4', 'first_lv5',
+            'again_lv1', 'again_lv2', 'again_lv3', 'again_lv4', 'again_lv5',
+        ]
+        with transaction.atomic():
+            for item in data:
+                config = Config.objects.filter(property=item['key']).first()
+                if item['value']:
+                    if item['key'] not in keys:
+                        raise CustomError(u'综合设置属性[%s]不存在' % item['key'])
+                    else:
+                        item['value'] = item['value'].strip()
+
+                    if config:
+                        config.value = item['value']
+                        config.save()
+                    else:
+                        Config.objects.create(property=item['key'], value=item['value'])
+            BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'修改系统配置',data)
+        return response_ok()
+

+ 82 - 0
apps/customer/serializers.py

@@ -0,0 +1,82 @@
+# coding=utf-8
+
+from django.contrib.auth import get_user_model
+
+from rest_framework import serializers
+from rest_framework_jwt.settings import api_settings
+
+from apps.customer.models import *
+from apps.log.models import BizLog
+
+
+User = get_user_model()
+jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
+jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
+
+
+class WechatLoginSerializer(serializers.Serializer):
+    def validate(self, attrs):
+        code = self.initial_data.get('code')
+        appid = self.initial_data.get('appid')
+
+        if code and appid:
+            wx_customer = CustomerWechat.login(code, appid)
+            if not wx_customer.customer:
+                return {
+                    'bind': 0,
+                    'openid': wx_customer.openid
+                }
+
+            user = wx_customer.customer.user
+            if not user.is_active:
+                msg = '用户帐户已禁用.'
+                raise serializers.ValidationError(msg)
+
+            payload = jwt_payload_handler(user)
+            BizLog.objects.addnew(user, BizLog.INSERT, u'用户微信登录,username=%s' % user.username)
+            return {
+                'bind': 1,
+                'token': jwt_encode_handler(payload),
+                'openid': wx_customer.openid,
+                'name': wx_customer.customer.name or '',
+                'tel': wx_customer.customer.tel or '',
+                'face': wx_customer.customer.face and wx_customer.customer.face.get_path() or '',
+                'gender': wx_customer.customer.gender or 0,
+            }
+
+        else:
+            msg = '参数无效'
+            raise serializers.ValidationError(msg)
+
+
+class WechatBindSerializer(serializers.Serializer):
+    def validate(self, attrs):
+        appid = self.initial_data.get('appid')
+        openid = self.initial_data.get('openid')
+        phoneEncryptedData = self.initial_data.get('encryptedData')
+        phoneIv = self.initial_data.get('iv')
+
+        if openid and phoneEncryptedData and phoneIv:
+            customer = CustomerWechat.bindWechat(appid, openid, phoneEncryptedData, phoneIv)
+            user = customer.user
+            payload = jwt_payload_handler(user)
+            BizLog.objects.addnew(user, BizLog.INSERT, u'用户微信登录,username=%s' % user.username)
+            return {
+                'token': jwt_encode_handler(payload),
+                'name': customer.name or '',
+                'tel': customer.tel or '',
+                'face': customer.face and customer.face.get_path() or '',
+                'gender': customer.gender or 0
+            }
+
+        else:
+            msg = '参数无效'
+            raise serializers.ValidationError(msg)
+
+
+class CustomerSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Customer
+        fields = '__all__'
+

+ 14 - 0
apps/customer/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'^code2Session/$', WxLoginView.as_view()),
+    url(r'^wxbind/$', WxBindView.as_view()),
+    url(r'^setUserInfo/$', SetUserInfoView.as_view()),
+    url(r'^token/refresh/', CustomerRefreshTokenView),
+    url(r'^token/verify/', CustomerVerifyTokenView),
+]

+ 69 - 0
apps/customer/views.py

@@ -0,0 +1,69 @@
+# coding=utf-8
+
+from django.db import transaction
+
+from rest_framework.views import APIView
+from rest_framework_jwt.views import VerifyJSONWebToken,RefreshJSONWebToken
+from rest_framework.serializers import ValidationError
+
+from utils import response_ok, response_error
+from utils.permission import IsCustomer
+
+from apps.customer.serializers import WechatLoginSerializer, WechatBindSerializer
+from apps.log.models import BizLog
+
+
+class WxLoginView(APIView):
+    serializer_class = WechatLoginSerializer
+
+    def post(self, request, *args, **kwargs):
+        ser = self.serializer_class(data=request.data)
+        if ser.is_valid():
+            return response_ok(ser.validated_data)
+        else:
+            return response_error('参数错误')
+
+
+class WxBindView(APIView):
+    serializer_class = WechatBindSerializer
+
+    def post(self, request, *args, **kwargs):
+        ser = self.serializer_class(data=request.data)
+        if ser.is_valid():
+            return response_ok(ser.validated_data)
+        else:
+            return response_error('参数错误')
+
+
+class SetUserInfoView(APIView):
+    permission_classes = [IsCustomer, ]
+
+    def post(self, request, *args, **kwargs):
+        appid = request.POST.get('appid')
+        openid = request.POST.get('openid')
+        encryptedData = request.POST.get('encryptedData')
+        iv = request.POST.get('iv')
+
+        with transaction.atomic():
+            face, name = request.customer.setInfo(appid, openid, encryptedData, iv)
+        return response_ok({'face': face, 'name': name})
+
+
+class CustomerRefreshTokenView(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] + ']')
+
+
+class CustomerVerifyTokenView(VerifyJSONWebToken):
+    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] + ']')

+ 89 - 0
apps/employee/serializers.py

@@ -0,0 +1,89 @@
+# coding=utf-8
+
+import json
+
+from django.contrib.auth import get_user_model, authenticate
+
+from rest_framework import serializers
+from rest_framework_jwt.serializers import JSONWebTokenSerializer
+from rest_framework_jwt.settings import api_settings
+
+from apps.employee.models import Employee
+from apps.log.models import BizLog
+
+from utils import get_remote_addr
+from utils.exceptions import CustomError
+
+User = get_user_model()
+jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
+jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
+
+
+class JWTSerializer(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_employee():
+                    msg = u'非工作账号,禁止登录'
+                    raise serializers.ValidationError(msg)
+
+                if not user.is_active:
+                    msg = u'禁用帐户,禁止登录'
+                    BizLog.objects.addnew(user, BizLog.INSERT, u'禁用帐户[%s]尝试登录系统,IP[%s]' % (user.username, get_remote_addr(self.request)))
+                    raise serializers.ValidationError(msg)
+
+                payload = jwt_payload_handler(user)
+                BizLog.objects.addnew(user, BizLog.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,
+                }
+            else:
+                msg = u'账号或者密码错误!'
+                raise serializers.ValidationError(msg)
+        else:
+            msg = u'必须包含“{username field}”和“password.'
+            msg = msg.format(username_field=self.username_field)
+            raise serializers.ValidationError(msg)
+
+
+class EmployeeSerializer(serializers.ModelSerializer):
+    password = serializers.CharField(source='user.password', write_only=True, allow_blank=True)
+    username = serializers.CharField(source='user.username')
+    is_active = serializers.IntegerField(source='user.is_active')
+    gender_text = serializers.CharField(source='get_gender_display', read_only=True)
+    create_time = serializers.DateTimeField(source='user.date_joined', format='%Y-%m-%d %H:%M', read_only=True)
+    is_active_text = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Employee
+        fields = '__all__'
+
+    def get_is_active_text(self, obj):
+        if obj.user.is_active:
+            return u'是'
+        return u'否'
+
+    def create(self, validated_data):
+        if validated_data['user']['password'].strip() == '':
+            raise CustomError(u'密码不能为空!')
+        user = User.objects.create_employee(validated_data['user']['username'], validated_data['user']['password'], is_active=validated_data['user']['is_active'])
+        validated_data['user'] = user
+        instance = super(EmployeeSerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        if 'user' in validated_data:
+            instance.user.update_item(validated_data['user'])
+            validated_data.pop('user')
+        instance = super(EmployeeSerializer, self).update(instance, validated_data)
+        return instance

+ 15 - 0
apps/employee/urls.py

@@ -0,0 +1,15 @@
+# coding=utf-8
+
+from django.conf.urls import url
+from rest_framework.routers import SimpleRouter
+from apps.employee.views import *
+
+urlpatterns = [
+    url(r'^login/$', LoginView.as_view()),
+    url(r'^token_refresh/$', RefreshTokenView.as_view()),
+    url(r'^change_password/$', ChangePasswrodView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'', EmployeeViewSet)
+urlpatterns += router.urls

+ 99 - 0
apps/employee/views.py

@@ -0,0 +1,99 @@
+# coding=utf-8
+
+import json
+
+from django.db import transaction
+from django.contrib.auth import get_user_model
+from rest_framework.views import APIView
+from rest_framework.serializers import ValidationError
+from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken
+
+from utils import response_error, response_ok, get_remote_addr
+from utils.permission import IsEmployee
+from utils.custom_modelviewset import CustomModelViewSet
+from utils.exceptions import CustomError
+
+from apps.employee.serializers import JWTSerializer, EmployeeSerializer
+from apps.employee.filters import EmployeeFilter
+from apps.employee.models import Employee
+from apps.log.models import BizLog
+
+User = get_user_model()
+
+
+class LoginView(ObtainJSONWebToken):
+    serializer_class = JWTSerializer
+
+    def post(self, request, *args, **kwargs):
+        '''
+        职工登录
+        :param:
+        :return:
+        '''
+        try:
+            ser = self.serializer_class(data=request.data)
+            ser.request = request
+            if ser.is_valid(raise_exception=True):
+                return response_ok(ser.validated_data)
+        except ValidationError as e:
+            return response_error(e.detail['error'][0])
+        except CustomError as e:
+            return response_error(str(e))
+
+
+class RefreshTokenView(RefreshJSONWebToken):
+
+    def post(self, request, *args, **kwargs):
+        try:
+            ser = self.serializer_class(data=request.data)
+            if ser.is_valid(raise_exception=True):
+                data = {
+                    'token': ser.validated_data['token'],
+                    'user_id': ser.validated_data['user'].id,
+                    'username': ser.validated_data['user'].username
+                }
+                return response_ok(data)
+        except ValidationError as e:
+            return response_ok({'error': True})
+        except CustomError as e:
+            return response_error(str(e))
+
+
+class ChangePasswrodView(APIView):
+    permission_classes = [IsEmployee, ]
+
+    def post(self, request):
+        '''
+        职工修改密码
+        :param request:
+        :return:
+        '''
+        data = json.loads(request.body)
+        with transaction.atomic():
+            request.user.change_password(data['new_password'], data['confirm_password'], data['old_password'])
+            request.user.save()
+            BizLog.objects.addnew(request.user, BizLog.INSERT, u'[%s]修改密码,IP[%s]' % (request.user.username, get_remote_addr(self.request)))
+        return response_ok()
+
+
+class EmployeeViewSet(CustomModelViewSet):
+    permission_classes = [IsEmployee, ]
+    queryset = Employee.objects.filter()
+    serializer_class = EmployeeSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter()
+        f = EmployeeFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(EmployeeViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'添加职工[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(EmployeeViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.UPDATE, u'修改职工信息[%s],id=%d' % (instance.name, instance.id), validated_data)

+ 36 - 0
apps/option/serializers.py

@@ -0,0 +1,36 @@
+# coding=utf-8
+
+from rest_framework import serializers
+
+from utils.exceptions import CustomError
+
+from apps.option.models import Option
+
+
+class OptionSerializer(serializers.ModelSerializer):
+    type_name = serializers.CharField(source='get_type_display', read_only=True)
+    enable_text = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Option
+        fields = '__all__'
+
+    def get_enable_text(self, obj):
+        if obj.enable:
+            return u'是'
+        return u'否'
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        is_exist = Option.is_exist(validated_data['type'], validated_data['name'])
+        if is_exist:
+            raise CustomError(u'自定义项[%s]已存在!' % validated_data['name'])
+        instance = super(OptionSerializer, self).create(validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        is_exist = Option.is_exist(validated_data['type'], validated_data['name'], instance.id)
+        if is_exist:
+            raise CustomError(u'自定义项[%s]已存在!' % validated_data['name'])
+        instance = super(OptionSerializer, self).update(instance, validated_data)
+        return instance

+ 13 - 0
apps/option/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'', OptionViewSet)
+urlpatterns += router.urls

+ 52 - 0
apps/option/views.py

@@ -0,0 +1,52 @@
+# coding=utf-8
+import json
+from utils.custom_modelviewset import CustomModelViewSet
+from rest_framework.views import APIView
+from rest_framework.decorators import action
+from django.db import transaction
+
+
+from apps.option.models import Option
+from apps.option.serializers import OptionSerializer
+from apps.option.filters import OptionFilter
+from apps.log.models import BizLog
+from utils import response_ok, response_error
+from utils.exceptions import CustomError
+from utils.permission import IsEmployee
+
+
+class DictView(APIView):
+    permission_classes = [IsEmployee, ]
+
+    def get(self, request):
+        ret = {
+            'types':Option.TYPE_CHOICES,
+        }
+        return response_ok(ret)
+
+
+class OptionViewSet(CustomModelViewSet):
+    permission_classes = [IsEmployee, ]
+    queryset = Option.objects.filter()
+    serializer_class = OptionSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter()
+        f = OptionFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(OptionViewSet,self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'添加自定义项[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(OptionViewSet,self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.UPDATE, u'修改自定义项[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+
+
+

+ 10 - 0
requirements

@@ -0,0 +1,10 @@
+django==2.2.5
+django-filter
+djangorestframework
+djangorestframework-jwt
+django-cors-headers
+mysqlclient
+Pillow
+pycryptodome
+requests
+xmltodict

+ 95 - 0
uis/layuiadmin/config.js

@@ -0,0 +1,95 @@
+/**
+
+ @Name:layuiAdmin iframe版全局配置
+ @Author:贤心
+ @Site:http://www.layui.com/admin/
+ @License:LPPL(layui付费产品协议)
+    
+ */
+ 
+layui.define(['laytpl', 'layer', 'element', 'util'], function(exports){
+  exports('setter', {
+    container: 'LAY_app' //容器ID
+    ,base: layui.cache.base //记录静态资源所在路径
+    ,views: layui.cache.base + 'tpl/' //动态模板所在目录
+    ,entry: 'index' //默认视图文件名
+    ,engine: '.html' //视图文件后缀名
+    ,pageTabs: true //是否开启页面选项卡功能。iframe 常规版推荐开启
+    
+    ,name: 'layuiAdmin'
+    ,tableName: 'layuiAdmin' //本地存储表名
+    ,MOD_NAME: 'admin' //模块事件名
+    
+    ,debug: false //是否开启调试模式。如开启,接口异常时会抛出异常 URL 等信息
+
+    //自定义请求字段
+    ,request: {
+      userId: 'USER-ID',
+      tokenName: 'Authorization' //自动携带 token 的字段名(如:access_token)。可设置 false 不携带。
+    }
+    
+    //自定义响应字段
+    ,response: {
+      statusName: 'code' //数据状态的字段名称
+      ,statusCode: {
+        ok: 0 //数据状态一切正常的状态码
+        ,logout: 460 //登录状态失效的状态码
+      }
+      ,msgName: 'msg' //状态信息的字段名称
+      ,dataName: 'data' //数据详情的字段名称
+    }
+    
+    //扩展的第三方模块
+    ,extend: [
+      'echarts', //echarts 核心包
+      'echartsTheme' //echarts 主题
+    ]
+    
+    //主题配置
+    ,theme: {
+      //配色方案,如果用户未设置主题,第一个将作为默认
+      color: [{
+        main: '#20222A' //主题色
+        ,selected: '#009688' //选中色
+        ,alias: 'default' //默认别名
+      },{
+        main: '#03152A'
+        ,selected: '#3B91FF'
+        ,alias: 'dark-blue' //藏蓝
+      },{
+        main: '#2E241B'
+        ,selected: '#A48566'
+        ,alias: 'coffee' //咖啡
+      },{
+        main: '#50314F'
+        ,selected: '#7A4D7B'
+        ,alias: 'purple-red' //紫红
+      },{
+        main: '#344058'
+        ,logo: '#1E9FFF'
+        ,selected: '#1E9FFF'
+        ,alias: 'ocean' //海洋
+      },{
+        main: '#3A3D49'
+        ,logo: '#2F9688'
+        ,selected: '#5FB878'
+        ,alias: 'green' //墨绿
+      },{
+        main: '#20222A'
+        ,logo: '#F78400'
+        ,selected: '#F78400'
+        ,alias: 'red' //橙色
+      },{
+        main: '#28333E'
+        ,logo: '#AA3130'
+        ,selected: '#AA3130'
+        ,alias: 'fashion-red' //时尚红
+      },{
+        main: '#24262F'
+        ,logo: '#3A3D49'
+        ,selected: '#009688'
+        ,alias: 'classic-black' //经典黑
+      }]
+    }
+  });
+});

文件差异内容过多而无法显示
+ 1 - 0
uis/layuiadmin/layui/css/layui.css


文件差异内容过多而无法显示
+ 1 - 0
uis/layuiadmin/layui/css/layui.mobile.css


+ 2 - 0
uis/layuiadmin/layui/css/modules/code.css

@@ -0,0 +1,2 @@
+/** layui-v2.4.5 MIT License By https://www.layui.com */
+ html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}

文件差异内容过多而无法显示
+ 1 - 0
uis/layuiadmin/layui/css/modules/laydate/default/laydate.css


二进制
uis/layuiadmin/layui/css/modules/layer/default/icon-ext.png


二进制
uis/layuiadmin/layui/css/modules/layer/default/icon.png


文件差异内容过多而无法显示
+ 1 - 0
uis/layuiadmin/layui/css/modules/layer/default/layer.css


二进制
uis/layuiadmin/layui/css/modules/layer/default/loading-0.gif


二进制
uis/layuiadmin/layui/css/modules/layer/default/loading-1.gif


二进制
uis/layuiadmin/layui/css/modules/layer/default/loading-2.gif


二进制
uis/layuiadmin/layui/font/iconfont.eot


文件差异内容过多而无法显示
+ 25 - 0
uis/layuiadmin/layui/font/iconfont.svg


二进制
uis/layuiadmin/layui/font/iconfont.ttf


二进制
uis/layuiadmin/layui/font/iconfont.woff


二进制
uis/layuiadmin/layui/images/face/0.gif


二进制
uis/layuiadmin/layui/images/face/1.gif


二进制
uis/layuiadmin/layui/images/face/10.gif


二进制
uis/layuiadmin/layui/images/face/11.gif


二进制
uis/layuiadmin/layui/images/face/12.gif


二进制
uis/layuiadmin/layui/images/face/13.gif


二进制
uis/layuiadmin/layui/images/face/14.gif


二进制
uis/layuiadmin/layui/images/face/15.gif


二进制
uis/layuiadmin/layui/images/face/16.gif


二进制
uis/layuiadmin/layui/images/face/17.gif


二进制
uis/layuiadmin/layui/images/face/18.gif


二进制
uis/layuiadmin/layui/images/face/19.gif


二进制
uis/layuiadmin/layui/images/face/2.gif


二进制
uis/layuiadmin/layui/images/face/20.gif


二进制
uis/layuiadmin/layui/images/face/21.gif


二进制
uis/layuiadmin/layui/images/face/22.gif


二进制
uis/layuiadmin/layui/images/face/23.gif


二进制
uis/layuiadmin/layui/images/face/24.gif


二进制
uis/layuiadmin/layui/images/face/25.gif


二进制
uis/layuiadmin/layui/images/face/26.gif


二进制
uis/layuiadmin/layui/images/face/27.gif


二进制
uis/layuiadmin/layui/images/face/28.gif


二进制
uis/layuiadmin/layui/images/face/29.gif


二进制
uis/layuiadmin/layui/images/face/3.gif


二进制
uis/layuiadmin/layui/images/face/30.gif


二进制
uis/layuiadmin/layui/images/face/31.gif


二进制
uis/layuiadmin/layui/images/face/32.gif


二进制
uis/layuiadmin/layui/images/face/33.gif


二进制
uis/layuiadmin/layui/images/face/34.gif


二进制
uis/layuiadmin/layui/images/face/35.gif


二进制
uis/layuiadmin/layui/images/face/36.gif


二进制
uis/layuiadmin/layui/images/face/37.gif


二进制
uis/layuiadmin/layui/images/face/38.gif


二进制
uis/layuiadmin/layui/images/face/39.gif


二进制
uis/layuiadmin/layui/images/face/4.gif


二进制
uis/layuiadmin/layui/images/face/40.gif


二进制
uis/layuiadmin/layui/images/face/41.gif


二进制
uis/layuiadmin/layui/images/face/42.gif


二进制
uis/layuiadmin/layui/images/face/43.gif


二进制
uis/layuiadmin/layui/images/face/44.gif


二进制
uis/layuiadmin/layui/images/face/45.gif


二进制
uis/layuiadmin/layui/images/face/46.gif


二进制
uis/layuiadmin/layui/images/face/47.gif


二进制
uis/layuiadmin/layui/images/face/48.gif


二进制
uis/layuiadmin/layui/images/face/49.gif


二进制
uis/layuiadmin/layui/images/face/5.gif


二进制
uis/layuiadmin/layui/images/face/50.gif


二进制
uis/layuiadmin/layui/images/face/51.gif


二进制
uis/layuiadmin/layui/images/face/52.gif


二进制
uis/layuiadmin/layui/images/face/53.gif


二进制
uis/layuiadmin/layui/images/face/54.gif


二进制
uis/layuiadmin/layui/images/face/55.gif


二进制
uis/layuiadmin/layui/images/face/56.gif


二进制
uis/layuiadmin/layui/images/face/57.gif


二进制
uis/layuiadmin/layui/images/face/58.gif


二进制
uis/layuiadmin/layui/images/face/59.gif


二进制
uis/layuiadmin/layui/images/face/6.gif


二进制
uis/layuiadmin/layui/images/face/60.gif


二进制
uis/layuiadmin/layui/images/face/61.gif


二进制
uis/layuiadmin/layui/images/face/62.gif


二进制
uis/layuiadmin/layui/images/face/63.gif


二进制
uis/layuiadmin/layui/images/face/64.gif


二进制
uis/layuiadmin/layui/images/face/65.gif


二进制
uis/layuiadmin/layui/images/face/66.gif


二进制
uis/layuiadmin/layui/images/face/67.gif


部分文件因为文件数量过多而无法显示