jiaweiqi il y a 3 ans
commit
5b063835bd
100 fichiers modifiés avec 1104 ajouts et 0 suppressions
  1. 12 0
      .gitignore
  2. 0 0
      apps/__init__.py
  3. 1 0
      apps/account/__init__.py
  4. 15 0
      apps/account/filters.py
  5. 0 0
      apps/account/migrations/__init__.py
  6. 126 0
      apps/account/models.py
  7. 0 0
      apps/customer/__init__.py
  8. 12 0
      apps/customer/filters.py
  9. 0 0
      apps/customer/migrations/__init__.py
  10. 74 0
      apps/customer/models.py
  11. 68 0
      apps/customer/serializers.py
  12. 16 0
      apps/customer/urls.py
  13. 91 0
      apps/customer/views.py
  14. 0 0
      apps/log/__init__.py
  15. 0 0
      apps/log/migrations/__init__.py
  16. 55 0
      apps/log/models.py
  17. 0 0
      apps/vehicle/__init__.py
  18. 0 0
      apps/vehicle/migrations/__init__.py
  19. 24 0
      apps/vehicle/models.py
  20. 0 0
      apps/wechat/__init__.py
  21. 0 0
      apps/wechat/migrations/__init__.py
  22. 29 0
      apps/wechat/models.py
  23. 0 0
      apps/xgjsms/__init__.py
  24. 0 0
      apps/xgjsms/migrations/__init__.py
  25. 147 0
      apps/xgjsms/models.py
  26. 0 0
      car_net/__init__.py
  27. 229 0
      car_net/settings.py
  28. 31 0
      car_net/urls.py
  29. 16 0
      car_net/wsgi.py
  30. 21 0
      manage.py
  31. 11 0
      requirements
  32. 95 0
      uis/layuiadmin/config.js
  33. 1 0
      uis/layuiadmin/layui/css/layui.css
  34. 1 0
      uis/layuiadmin/layui/css/layui.mobile.css
  35. 2 0
      uis/layuiadmin/layui/css/modules/code.css
  36. 1 0
      uis/layuiadmin/layui/css/modules/laydate/default/laydate.css
  37. BIN
      uis/layuiadmin/layui/css/modules/layer/default/icon-ext.png
  38. BIN
      uis/layuiadmin/layui/css/modules/layer/default/icon.png
  39. 1 0
      uis/layuiadmin/layui/css/modules/layer/default/layer.css
  40. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-0.gif
  41. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-1.gif
  42. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-2.gif
  43. BIN
      uis/layuiadmin/layui/font/iconfont.eot
  44. 25 0
      uis/layuiadmin/layui/font/iconfont.svg
  45. BIN
      uis/layuiadmin/layui/font/iconfont.ttf
  46. BIN
      uis/layuiadmin/layui/font/iconfont.woff
  47. BIN
      uis/layuiadmin/layui/images/face/0.gif
  48. BIN
      uis/layuiadmin/layui/images/face/1.gif
  49. BIN
      uis/layuiadmin/layui/images/face/10.gif
  50. BIN
      uis/layuiadmin/layui/images/face/11.gif
  51. BIN
      uis/layuiadmin/layui/images/face/12.gif
  52. BIN
      uis/layuiadmin/layui/images/face/13.gif
  53. BIN
      uis/layuiadmin/layui/images/face/14.gif
  54. BIN
      uis/layuiadmin/layui/images/face/15.gif
  55. BIN
      uis/layuiadmin/layui/images/face/16.gif
  56. BIN
      uis/layuiadmin/layui/images/face/17.gif
  57. BIN
      uis/layuiadmin/layui/images/face/18.gif
  58. BIN
      uis/layuiadmin/layui/images/face/19.gif
  59. BIN
      uis/layuiadmin/layui/images/face/2.gif
  60. BIN
      uis/layuiadmin/layui/images/face/20.gif
  61. BIN
      uis/layuiadmin/layui/images/face/21.gif
  62. BIN
      uis/layuiadmin/layui/images/face/22.gif
  63. BIN
      uis/layuiadmin/layui/images/face/23.gif
  64. BIN
      uis/layuiadmin/layui/images/face/24.gif
  65. BIN
      uis/layuiadmin/layui/images/face/25.gif
  66. BIN
      uis/layuiadmin/layui/images/face/26.gif
  67. BIN
      uis/layuiadmin/layui/images/face/27.gif
  68. BIN
      uis/layuiadmin/layui/images/face/28.gif
  69. BIN
      uis/layuiadmin/layui/images/face/29.gif
  70. BIN
      uis/layuiadmin/layui/images/face/3.gif
  71. BIN
      uis/layuiadmin/layui/images/face/30.gif
  72. BIN
      uis/layuiadmin/layui/images/face/31.gif
  73. BIN
      uis/layuiadmin/layui/images/face/32.gif
  74. BIN
      uis/layuiadmin/layui/images/face/33.gif
  75. BIN
      uis/layuiadmin/layui/images/face/34.gif
  76. BIN
      uis/layuiadmin/layui/images/face/35.gif
  77. BIN
      uis/layuiadmin/layui/images/face/36.gif
  78. BIN
      uis/layuiadmin/layui/images/face/37.gif
  79. BIN
      uis/layuiadmin/layui/images/face/38.gif
  80. BIN
      uis/layuiadmin/layui/images/face/39.gif
  81. BIN
      uis/layuiadmin/layui/images/face/4.gif
  82. BIN
      uis/layuiadmin/layui/images/face/40.gif
  83. BIN
      uis/layuiadmin/layui/images/face/41.gif
  84. BIN
      uis/layuiadmin/layui/images/face/42.gif
  85. BIN
      uis/layuiadmin/layui/images/face/43.gif
  86. BIN
      uis/layuiadmin/layui/images/face/44.gif
  87. BIN
      uis/layuiadmin/layui/images/face/45.gif
  88. BIN
      uis/layuiadmin/layui/images/face/46.gif
  89. BIN
      uis/layuiadmin/layui/images/face/47.gif
  90. BIN
      uis/layuiadmin/layui/images/face/48.gif
  91. BIN
      uis/layuiadmin/layui/images/face/49.gif
  92. BIN
      uis/layuiadmin/layui/images/face/5.gif
  93. BIN
      uis/layuiadmin/layui/images/face/50.gif
  94. BIN
      uis/layuiadmin/layui/images/face/51.gif
  95. BIN
      uis/layuiadmin/layui/images/face/52.gif
  96. BIN
      uis/layuiadmin/layui/images/face/53.gif
  97. BIN
      uis/layuiadmin/layui/images/face/54.gif
  98. BIN
      uis/layuiadmin/layui/images/face/55.gif
  99. BIN
      uis/layuiadmin/layui/images/face/56.gif
  100. BIN
      uis/layuiadmin/layui/images/face/57.gif

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files. 
+
+**/migrations/*
+!**/migrations/__init__.py
+
+*.py[cod]
+local_settings.*
+*.bat
+*.txt
+*.whl
+venv
+.idea

+ 0 - 0
apps/__init__.py


+ 1 - 0
apps/account/__init__.py

@@ -0,0 +1 @@
+#coding=utf-8

+ 15 - 0
apps/account/filters.py

@@ -0,0 +1,15 @@
+# coding=utf-8
+import django_filters
+
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+
+
+class UserFilter(django_filters.FilterSet):
+    username = django_filters.CharFilter(field_name='username', lookup_expr='icontains')
+    is_active = django_filters.CharFilter(field_name='is_active')
+
+    class Meta:
+        model = User
+        fields = ['username', 'is_active']

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


+ 126 - 0
apps/account/models.py

@@ -0,0 +1,126 @@
+# coding=utf-8
+
+from django.db import models
+from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager, Group
+from django.utils import timezone
+from django.conf import settings
+
+from rest_framework.utils import model_meta
+
+from utils.exceptions import CustomError
+
+
+class UserManager(BaseUserManager):
+    def create_administrator(self, username, password=None, **extra_fields):
+        return self.create_user(User.ADMINSTRATOR, username, password, **extra_fields)
+
+    def create_customer(self, username, password=None, **extra_fields):
+        return self.create_user(User.CUSTOMER, username, password, **extra_fields)
+
+    def create_superuser(self, username, password, **extra_fields):
+        u = self.create_administrator(username, password, **extra_fields)
+        u.is_active = True
+        u.is_superuser = True
+        u.save(using=self._db)
+        return u
+
+    def create_user(self, type, username, password=None, **extra_fields):
+        if not username:
+            raise CustomError(u'请输入用户名!')
+        count = User.objects.filter(username=username).count()
+        if count > 0:
+            raise CustomError(u'该用户名已存在!')
+        user = self.model(
+            type=type,
+            username=username,
+            is_superuser=False,
+            last_login=timezone.now(),
+            **extra_fields
+        )
+
+        user.set_password(password)
+        user.save(using=self._db)
+        return user
+
+
+class User(AbstractBaseUser, PermissionsMixin):
+    ADMINSTRATOR = 1
+    CUSTOMER = 2
+
+    type = models.PositiveSmallIntegerField(verbose_name=u"类型", editable=False)
+    username = models.CharField(verbose_name=u'帐号', max_length=30, unique=True, db_index=True)
+    is_active = models.BooleanField(verbose_name=u'激活', default=True)
+    date_joined = models.DateTimeField(verbose_name=u'注册时间', default=timezone.now, editable=False)
+
+    name = models.CharField(max_length=20, verbose_name=u"姓名", null=True, blank=True)
+    gender = models.PositiveSmallIntegerField(choices=settings.GENDER_CHOICES, verbose_name=u'性别', null=True, blank=True)
+    face = models.CharField(max_length=200, verbose_name=u'头像', null=True, blank=True)
+    tel = models.CharField(max_length=15, verbose_name=u"手机", null=True, blank=True)
+
+    objects = UserManager()
+
+    USERNAME_FIELD = 'username'
+    REQUIRED_FIELDS = []
+
+    class Meta:
+        db_table = "auth_user"
+        verbose_name = u"用户"
+        unique_together = [
+            ('username')
+        ]
+        index_together = (
+            'date_joined',
+        )
+        ordering = ['-id']
+        default_permissions = ()
+
+    def __unicode__(self):
+        return self.username
+
+    def addAdministrator(self):
+        self.type = self.type | self.ADMINSTRATOR
+
+    def addCustomer(self):
+        self.type = self.type | self.CUSTOMER
+
+    def is_customer(self):
+        if self.type & self.CUSTOMER:
+            return True
+        return False
+
+    def is_administrator(self):
+        if self.type & self.ADMINSTRATOR:
+            return True
+        return False
+
+    def change_password(self, new_password, confirm_password, old_password):
+        if new_password != confirm_password:
+            raise CustomError(u'两次输入的密码不一致, 请检查')
+        if not self.check_password(old_password):
+            raise CustomError(u'原密码输入错误, 请检查')
+        self.set_password(new_password)
+
+    def update_item(self, validated_data):
+        def update():
+            info = model_meta.get_field_info(self)
+            for attr, value in validated_data.items():
+                if attr in info.relations and info.relations[attr].to_many:
+                    field = getattr(self, attr)
+                    field.set(value)
+                else:
+                    setattr(self, attr, value)
+
+        if not 'username' in validated_data:
+            raise CustomError(u'用户名不能为空!')
+        count = User.objects.filter(username=validated_data['username']).exclude(id=self.id).count()
+        if count > 0:
+            raise CustomError(u'该用户名已存在!')
+
+        if not 'password' in validated_data or not validated_data['password']:
+            validated_data['password'] = self.password
+            update()
+        else:
+            update()
+            self.set_password(validated_data['password'])
+        self.save()
+        return self

+ 0 - 0
apps/customer/__init__.py


+ 12 - 0
apps/customer/filters.py

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

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


+ 74 - 0
apps/customer/models.py

@@ -0,0 +1,74 @@
+# coding=utf-8
+
+from django.db import models
+from django.conf import settings
+from django.contrib.auth import get_user_model
+
+from apps.wechat.models import Wechat
+
+from utils.wx.wechat import WeChat
+from utils.wx.WXBizDataCrypt import WXBizDataCrypt
+from utils.exceptions import CustomError
+
+User = get_user_model()
+
+
+class Customer(models.Model):
+    app = models.ForeignKey(Wechat, verbose_name=u'小程序', on_delete=models.PROTECT, editable=False)
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, editable=False, related_name='customer_ref_user_id', on_delete=models.PROTECT, verbose_name=u'用户')
+    openid = models.CharField(max_length=512, verbose_name=u"openid")
+    session_key = models.CharField(max_length=512, verbose_name=u'session_key', null=True)
+
+    class Meta:
+        db_table = "customer"
+        verbose_name = u"客户管理"
+        ordering = ['-id']
+        unique_together = [
+            ('openid')
+        ]
+        index_together = (
+            'name',
+            'tel',
+        )
+        default_permissions = ()
+
+    @staticmethod
+    def login(code, appid):
+        app = Wechat.getByAppid(appid)
+        res = WeChat.code2Session(appid, app.secret, code)
+        instance = Customer.objects.filter(openid=res['openid'], app__appid=appid).first()
+        if not instance:
+            instance = Customer.objects.create(
+                app=app,
+                openid=res['openid'],
+                session_key=res['session_key']
+            )
+        else:
+            instance.session_key = res['session_key']
+            instance.save()
+        return instance
+
+    @staticmethod
+    def bindWechat(appid, openid, phoneEncryptedData, phoneIv):
+        customer = Customer.objects.filter(openid=openid, app__appid=appid).first()
+        if not customer:
+            raise CustomError(u'未找到相应的微信客户!')
+
+        pc = WXBizDataCrypt(appid, customer.session_key)
+        phon_data = pc.decrypt(phoneEncryptedData, phoneIv)
+
+        tel = phon_data['purePhoneNumber']
+
+        if customer.user:
+            if customer.user.username != tel:
+                raise CustomError(u'微信绑定的手机号与系统记录的不符!')
+            else:
+                return customer
+
+        user = User.objects.filter(username=tel).first()
+        if not user:
+            user = User.objects.create_customer(tel, password='', **{'tel': tel, 'name': tel, })
+
+        customer.user = user
+        customer.save()
+        return customer

+ 68 - 0
apps/customer/serializers.py

@@ -0,0 +1,68 @@
+# 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 Customer
+
+
+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:
+            customer = Customer.login(code, appid)
+            if not customer.user:
+                return {
+                    'openid': customer.openid,
+                }
+
+            user = customer.user
+            if not user.is_active:
+                msg = '用户帐户已禁用.'
+                raise serializers.ValidationError(msg)
+
+            payload = jwt_payload_handler(user)
+            return {
+                'token': jwt_encode_handler(payload),
+                'openid': customer.openid,
+                'name': user.name or '',
+                'tel': user.tel or '',
+                'face': user.face or '',
+            }
+
+        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 = Customer.bindWechat(appid, openid, phoneEncryptedData, phoneIv)
+            user = customer.user
+            payload = jwt_payload_handler(user)
+            return {
+                'token': jwt_encode_handler(payload),
+                'openid': customer.openid,
+                'name': user.name or '',
+                'tel': user.tel or '',
+                'face': user.face or '',
+            }
+
+        else:
+            msg = '参数无效'
+            raise serializers.ValidationError(msg)

+ 16 - 0
apps/customer/urls.py

@@ -0,0 +1,16 @@
+# coding=utf-8
+
+from django.conf.urls import url
+
+from .views import *
+
+urlpatterns = [
+    url(r'^token/refresh/', CustomerRefreshTokenView),
+    url(r'^token/verify/', CustomerVerifyTokenView),
+
+    url(r'^code2Session/$', WxLoginView.as_view()),
+    url(r'^wxbind/$', WxBindView.as_view()),
+    url(r'^setUserInfo/$', SetUserInfoView.as_view()),
+
+]
+

+ 91 - 0
apps/customer/views.py

@@ -0,0 +1,91 @@
+# coding=utf-8
+
+from django.db import transaction
+from django.conf import settings
+
+from rest_framework.views import APIView
+from rest_framework_jwt.views import ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken
+from rest_framework.serializers import ValidationError
+
+from utils import response_ok, response_error
+from utils.exceptions import CustomError
+from utils.permission import IsCustomerUser
+from utils.wx.WXBizDataCrypt import WXBizDataCrypt
+
+from apps.customer.serializers import WechatLoginSerializer, WechatBindSerializer
+from apps.customer.models import Customer
+
+
+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] + ']')
+
+
+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 = [IsCustomerUser, ]
+
+    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')
+
+        customer = Customer.objects.filter(openid=openid, app__appid=appid).first()
+        if not customer:
+            raise CustomError(u'未找到相应的微信客户!')
+        user = request.user
+        if customer.user and customer.user.id != user.id:
+            raise CustomError(u'该微信已同步其他客户!')
+        if not customer.user:
+            customer.user = user
+            customer.save()
+        pc = WXBizDataCrypt(appid, customer.session_key)
+        result = pc.decrypt(encryptedData, iv)
+        with transaction.atomic():
+            if result['gender'] == 1:
+                user.gender = settings.MALE
+            elif result['gender'] == 2:
+                user.gender = settings.FEMALE
+            else:
+                user.gender = settings.UNKNOW
+            user.name = result['nickName']
+            user.face = result['avatarUrl']
+            user.save()
+        return response_ok()

+ 0 - 0
apps/log/__init__.py


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


+ 55 - 0
apps/log/models.py

@@ -0,0 +1,55 @@
+# coding=utf-8
+
+import json
+import datetime
+
+from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+from utils.format import strftime, strfdate
+
+
+class BizLogManager(models.Manager):
+    def addnew(self, user, type, description, data=None):
+        def default(o):
+            if isinstance(o, datetime.datetime):
+                return strftime(o)
+            elif isinstance(o, datetime.date):
+                return strfdate(o)
+
+        row = self.model(user=user, type=type, description=description)
+        if data:
+            row.data = json.dumps(data, default=default)
+        row.save()
+        return row
+
+
+class BizLog(models.Model):
+    INSERT = 1
+    UPDATE = 2
+    DELETE = 3
+    TYPE_CHOICES = (
+        (INSERT, u'添加'),
+        (UPDATE, u'修改'),
+        (DELETE, u'删除'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类别")
+    description = models.CharField(max_length=1000, verbose_name=u"内容")
+    data = models.TextField(verbose_name=u"数据", null=True, blank=True)
+    create_time = models.DateTimeField(verbose_name=u"添加时间", default=timezone.now, editable=False)
+
+    objects = BizLogManager()
+
+    class Meta:
+        db_table = "system_log"
+        ordering = ['-id']
+        index_together = (
+            'create_time',
+            'type',
+        )
+        verbose_name = u"系统日志"
+        default_permissions = ()

+ 0 - 0
apps/vehicle/__init__.py


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


+ 24 - 0
apps/vehicle/models.py

@@ -0,0 +1,24 @@
+# coding=utf-8
+
+from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+
+class Wechat(models.Model):
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, editable=False, related_name='vehicle_ref_user_id', on_delete=models.PROTECT, verbose_name=u'用户')
+    tel = models.CharField(verbose_name=u'电话', max_length=50)
+    name = models.CharField(verbose_name=u'姓名', max_length=100)
+    number = models.CharField(verbose_name=u'车牌号', max_length=30)
+    model = models.CharField(verbose_name=u'车型', max_length=250)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now, editable=False)
+
+    class Meta:
+        db_table = "vehicle"
+        ordering = ['-id']
+        index_together = (
+            'tel',
+            'number',
+        )
+        verbose_name = u"设备信息"
+        default_permissions = ()

+ 0 - 0
apps/wechat/__init__.py


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


+ 29 - 0
apps/wechat/models.py

@@ -0,0 +1,29 @@
+# coding=utf-8
+
+from django.db import models
+
+from utils.exceptions import CustomError
+
+
+class Wechat(models.Model):
+    appid = models.CharField(max_length=512, verbose_name=u'小程序appid')
+    secret = models.CharField(max_length=512, verbose_name=u'小程序秘钥', null=True, blank=True)
+    agent_num = models.CharField(max_length=512, verbose_name=u'商户号', null=True, blank=True)
+    agent_key = models.CharField(max_length=512, verbose_name=u'商户密钥', null=True, blank=True)
+    sms_sign = models.CharField(verbose_name=u'短信SIGN', max_length=200, null=True, blank=True)
+    sms_token = models.CharField(verbose_name=u'短信TOKEN', max_length=200, null=True, blank=True)
+    Privacy_statement = models.TextField(verbose_name=u'隐私声明', null=True, blank=True)
+
+    class Meta:
+        db_table = "wechat"
+        ordering = ['-id']
+        index_together = ()
+        verbose_name = u"小程序"
+        default_permissions = ()
+
+    @staticmethod
+    def getByAppid(appid):
+        instance = Wechat.objects.filter(appid=appid).first()
+        if not instance:
+            raise CustomError(u'未找到相应的小程序!')
+        return instance

+ 0 - 0
apps/xgjsms/__init__.py


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


+ 147 - 0
apps/xgjsms/models.py

@@ -0,0 +1,147 @@
+# coding=utf-8
+
+import random
+from urllib import request, parse
+import re
+import math
+
+from django.db import models
+from django.utils import timezone
+
+from utils.exceptions import CustomError
+
+from apps.wechat.models import Wechat
+
+
+class XGJSMS:
+    def __init__(self):
+        self.SMS_URL = 'http://sms.zzliaoyuan.com:8088'
+        self.SMS_PRODUCT_NOTIFY = 30 # 通知短信的代码
+        self.success_result = {'Code': u'OK', 'Message': u'短信提交成功'}
+        self.false_result = {'Code': u'FALSE', 'Message': u'短信提交失败'}
+
+    def _do_request(self, req):
+        try:
+            resp = request.urlopen(req)
+        except Exception as e:
+
+            raise CustomError(u"短信平台错误")
+
+        if resp.getcode() != 200:
+            raise CustomError(u'短信平台错误')
+
+        return resp.read()
+
+    def send(self, sms_token, sms_sign, mobile, content):
+        content = u"【%s】%s" % (sms_sign, content)
+
+        url = '%s/balance/?token=%s' % (self.SMS_URL, sms_token)
+        req = request.Request(url)
+
+        try:
+            resp = self._do_request(req)
+        except CustomError as e:
+            self.false_result['Message'] = e.get_error_msg()
+            return self.false_result
+
+        p = '<Product id="%s">([\d]+?)</Product>' % self.SMS_PRODUCT_NOTIFY
+        r = re.search(p, resp.decode('utf-8'))
+        if r == None:
+            self.false_result['Message'] = u'短信平台账号余额不足'
+            return self.false_result
+        else:
+            balance = int(r.groups()[0])
+            fee_count = 1
+            if len(content) > 70:
+                fee_count = int(math.ceil(len(content) / 65.0))
+
+            if balance < 1 or balance < 1 * fee_count:
+                self.false_result['Message'] = u'短信平台账号余额不足'
+                return self.false_result
+
+        postDict = {'token': sms_token, 'product': self.SMS_PRODUCT_NOTIFY, 'numbers': mobile, 'content': content.encode('utf-8')}
+        postData = parse.urlencode(postDict).encode('utf-8')
+        url = self.SMS_URL + "/submit/"
+        req = request.Request(url, postData)
+        req.add_header('content-Type', "application/x-www-form-urlencoded; charset=UTF-8")
+
+        try:
+            resp = self._do_request(req)
+        except CustomError as e:
+            self.false_result['Message'] = e.get_error_msg()
+            return self.false_result
+
+        r = re.search("<Errors>([\s\S]+?)</Errors>", resp.decode('utf-8'))
+        if r != None:
+            self.false_result['Message'] = u'短信平台返回错误[%s]' % r.groups()[0]
+            return self.false_result
+
+        return self.success_result
+
+
+class XGJVCode(models.Model):
+    SUCCESSED = 0
+    FAILED = 1
+    STATUS_CHOICES = (
+        (SUCCESSED, u'发送成功'),
+        (FAILED, u'发送失败'),
+    )
+
+    vcode = models.CharField(verbose_name=u'验证码', max_length=4)
+    mobile = models.CharField(verbose_name=u'手机号', max_length=11)
+    create_time = models.DateTimeField(verbose_name=u'时间', auto_now_add=True)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"状态", editable=False, default=SUCCESSED)
+    error = models.CharField(verbose_name=u"错误原因", null=True, max_length=500)
+
+    class Meta:
+        db_table = "xgj_vcode"
+        verbose_name = u"销管佳短信验证码"
+        ordering = ['-id']
+        default_permissions = ()
+
+    @staticmethod
+    def send(mobile, appid, type, captcha_key=None, captcha=None):
+        # 参数说明:
+        # token:
+        # numbers: 手机号 以半角逗号分隔多个手机号码
+        # content: 短信内容,中文使用utf-8编码
+        # product:产品代码 只够买一个产品时可以省略该参数
+        # schedule:定时发送时间  非必填
+
+        if mobile == '' or not mobile:
+            raise CustomError(u"无效的手机号!")
+        app = Wechat.objects.filter(appid=appid).first()
+        if not app:
+            raise CustomError(u'参数无效')
+
+        vcs = XGJVCode.objects.filter(mobile=mobile, type=type).order_by('-id')
+        if vcs.count() > 0:
+            vc = vcs[0]
+            if (timezone.now() - vc.create_time).seconds < 60:
+                raise CustomError(u'获取太频繁,请稍后再试!')
+
+        code = str(random.randint(1234, 9999))
+        str_code = u'短信验证码为:' + code + u'。为了保障您的账户安全,请勿泄露给他人,有效期为10分钟。'
+        sms = XGJSMS()
+
+        result = sms.send(app.sms_token, app.sms_sign, mobile, str_code)
+        if result['Code'] == 'OK':
+            XGJVCode.objects.create(mobile=mobile, vcode=code, type=type)
+        else:
+            XGJVCode.objects.create(mobile=mobile, vcode=code, type=type, status=XGJVCode.FAILED, error=result['Message'])
+            raise CustomError(u"发送失败,请重试!")
+
+    @staticmethod
+    def verify(mobile, vcode, type):
+        if mobile is None or vcode is None:
+            raise CustomError(u'参数错误!')
+
+        vc = XGJVCode.objects.filter(mobile=mobile, type=type).order_by('-id').first()
+        if vc:
+            if vc.vcode == vcode:
+                if (timezone.now() - vc.create_time).seconds > 600:
+                    raise CustomError(u'验证码已过期!')
+            else:
+                raise CustomError(u'验证码错误!')
+        else:
+            raise CustomError(u'该手机号未发送短信验证码!')

+ 0 - 0
car_net/__init__.py


+ 229 - 0
car_net/settings.py

@@ -0,0 +1,229 @@
+# coding=utf-8
+"""
+Django settings for net_car project.
+
+Generated by 'django-admin startproject' using Django 2.2.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/2.2/ref/settings/
+"""
+
+import os, datetime
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'c+)5afbc(m&qe^8t7gc54w6f-*==b2(l0(8rqef-cbwtv*3n$w'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    #'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+
+    'corsheaders',
+    'rest_framework',
+    'rest_framework_jwt',
+    'django_filters',
+
+    'apps.account',
+    'apps.customer',
+    'apps.log',
+    'apps.xgjsms',
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'corsheaders.middleware.CorsMiddleware',
+]
+
+CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_ALLOW_METHODS = (
+    'DELETE',
+    'GET',
+    'OPTIONS',
+    'PATCH',
+    'POST',
+    'PUT',
+    'VIEW',
+)
+CORS_ALLOW_HEADERS = (
+    'XMLHttpRequest',
+    'X_FILENAME',
+    'accept-encoding',
+    'authorization',
+    'content-type',
+    'dnt',
+    'origin',
+    'token',
+    'user-agent',
+    'x-csrftoken',
+    'x-requested-with',
+    'Pragma',
+)
+
+ROOT_URLCONF = 'car_net.urls'
+AUTH_USER_MODEL = "account.User"
+AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'net_car.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.mysql',
+        'NAME': 'net_car',
+        'USER': 'root',
+        'PASSWORD': 'lyh123',
+        'HOST': '127.0.0.1',
+        'PORT': 3306,
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+UNKNOW = 0
+FEMALE = 1
+MALE = 2
+GENDER_CHOICES = (
+    (UNKNOW, u'未知'),
+    (FEMALE, u'女'),
+    (MALE, u'男'),
+)
+
+DEFAULT = 0
+PASS = 1
+REJECT = 2
+CHECK_STATE_CHOICES = (
+    (DEFAULT, u'未审核'),
+    (PASS, u'已通过'),
+    (REJECT, u'未通过'),
+)
+
+# Internationalization
+# https://docs.djangoproject.com/en/2.2/topics/i18n/
+
+LANGUAGE_CODE = 'zh-hans'
+
+TIME_ZONE = 'Asia/Shanghai'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = False
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/2.2/howto/static-files/
+
+STATIC_URL = '/static/'
+
+MEDIA_URL = '/up/'
+MEDIA_ROOT = os.path.join(BASE_DIR, "uis/up/")
+
+UIS_URL = '/'
+UIS_ROOT = os.path.join(BASE_DIR, "uis/")
+
+
+JWT_AUTH = {
+    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30),
+    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=360),
+    'JWT_ALLOW_REFRESH': True,
+}
+
+REST_FRAMEWORK = {
+    'DEFAULT_PERMISSION_CLASSES': (
+        'rest_framework.permissions.AllowAny',
+    ),
+    'DEFAULT_AUTHENTICATION_CLASSES': (
+        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
+        #'rest_framework.authentication.SessionAuthentication',
+        #'rest_framework.authentication.BasicAuthentication',
+    ),
+    'DEFAULT_PARSER_CLASSES': (
+        'rest_framework.parsers.JSONParser',
+        'rest_framework.parsers.FormParser',
+        'rest_framework.parsers.MultiPartParser'
+    ),
+    'DEFAULT_RENDERER_CLASSES': (
+        'rest_framework.renderers.JSONRenderer',
+        # 'rest_framework.renderers.BrowsableAPIRenderer',
+    ),
+    'DEFAULT_FILTER_BACKENDS': (
+        'django_filters.rest_framework.DjangoFilterBackend',
+    ),
+    #'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+    'DEFAULT_PAGINATION_CLASS': 'utils.pagination.CustomPagination',
+    #'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
+    'EXCEPTION_HANDLER': 'utils.handler.custom_exception_handler',
+    'PAGE_SIZE': 10,
+    'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
+    'NON_FIELD_ERRORS_KEY': "error",  # 序列化器错误KEY名称
+}
+
+# 导入本地设置
+try:
+    from net_car.local_settings import *
+except ImportError:
+    pass

+ 31 - 0
car_net/urls.py

@@ -0,0 +1,31 @@
+# coding=utf-8
+"""hagrid URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/2.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+
+from django.conf.urls import url, include
+from django.conf.urls.static import static
+from django.conf import settings
+
+from apps.dashboard.views import index, admin_index
+
+urlpatterns = [
+    url(r'^$', index),
+    url(r'^zzlyadmin/$', admin_index),
+    url(r'^customer/', include('apps.customer.urls')),
+]
+
+urlpatterns += static(settings.UIS_URL, document_root=settings.UIS_ROOT)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

+ 16 - 0
car_net/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for car_net project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'net_car.settings')
+
+application = get_wsgi_application()

+ 21 - 0
manage.py

@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'net_car.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 11 - 0
requirements

@@ -0,0 +1,11 @@
+django==2.2.5
+django-filter
+djangorestframework
+djangorestframework-jwt
+django-cors-headers
+mysqlclient
+aliyun-python-sdk-core
+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' //经典黑
+      }]
+    }
+  });
+});

Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
uis/layuiadmin/layui/css/layui.css


Fichier diff supprimé car celui-ci est trop grand
+ 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}

Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
uis/layuiadmin/layui/css/modules/laydate/default/laydate.css


BIN
uis/layuiadmin/layui/css/modules/layer/default/icon-ext.png


BIN
uis/layuiadmin/layui/css/modules/layer/default/icon.png


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
uis/layuiadmin/layui/css/modules/layer/default/layer.css


BIN
uis/layuiadmin/layui/css/modules/layer/default/loading-0.gif


BIN
uis/layuiadmin/layui/css/modules/layer/default/loading-1.gif


BIN
uis/layuiadmin/layui/css/modules/layer/default/loading-2.gif


BIN
uis/layuiadmin/layui/font/iconfont.eot


Fichier diff supprimé car celui-ci est trop grand
+ 25 - 0
uis/layuiadmin/layui/font/iconfont.svg


BIN
uis/layuiadmin/layui/font/iconfont.ttf


BIN
uis/layuiadmin/layui/font/iconfont.woff


BIN
uis/layuiadmin/layui/images/face/0.gif


BIN
uis/layuiadmin/layui/images/face/1.gif


BIN
uis/layuiadmin/layui/images/face/10.gif


BIN
uis/layuiadmin/layui/images/face/11.gif


BIN
uis/layuiadmin/layui/images/face/12.gif


BIN
uis/layuiadmin/layui/images/face/13.gif


BIN
uis/layuiadmin/layui/images/face/14.gif


BIN
uis/layuiadmin/layui/images/face/15.gif


BIN
uis/layuiadmin/layui/images/face/16.gif


BIN
uis/layuiadmin/layui/images/face/17.gif


BIN
uis/layuiadmin/layui/images/face/18.gif


BIN
uis/layuiadmin/layui/images/face/19.gif


BIN
uis/layuiadmin/layui/images/face/2.gif


BIN
uis/layuiadmin/layui/images/face/20.gif


BIN
uis/layuiadmin/layui/images/face/21.gif


BIN
uis/layuiadmin/layui/images/face/22.gif


BIN
uis/layuiadmin/layui/images/face/23.gif


BIN
uis/layuiadmin/layui/images/face/24.gif


BIN
uis/layuiadmin/layui/images/face/25.gif


BIN
uis/layuiadmin/layui/images/face/26.gif


BIN
uis/layuiadmin/layui/images/face/27.gif


BIN
uis/layuiadmin/layui/images/face/28.gif


BIN
uis/layuiadmin/layui/images/face/29.gif


BIN
uis/layuiadmin/layui/images/face/3.gif


BIN
uis/layuiadmin/layui/images/face/30.gif


BIN
uis/layuiadmin/layui/images/face/31.gif


BIN
uis/layuiadmin/layui/images/face/32.gif


BIN
uis/layuiadmin/layui/images/face/33.gif


BIN
uis/layuiadmin/layui/images/face/34.gif


BIN
uis/layuiadmin/layui/images/face/35.gif


BIN
uis/layuiadmin/layui/images/face/36.gif


BIN
uis/layuiadmin/layui/images/face/37.gif


BIN
uis/layuiadmin/layui/images/face/38.gif


BIN
uis/layuiadmin/layui/images/face/39.gif


BIN
uis/layuiadmin/layui/images/face/4.gif


BIN
uis/layuiadmin/layui/images/face/40.gif


BIN
uis/layuiadmin/layui/images/face/41.gif


BIN
uis/layuiadmin/layui/images/face/42.gif


BIN
uis/layuiadmin/layui/images/face/43.gif


BIN
uis/layuiadmin/layui/images/face/44.gif


BIN
uis/layuiadmin/layui/images/face/45.gif


BIN
uis/layuiadmin/layui/images/face/46.gif


BIN
uis/layuiadmin/layui/images/face/47.gif


BIN
uis/layuiadmin/layui/images/face/48.gif


BIN
uis/layuiadmin/layui/images/face/49.gif


BIN
uis/layuiadmin/layui/images/face/5.gif


BIN
uis/layuiadmin/layui/images/face/50.gif


BIN
uis/layuiadmin/layui/images/face/51.gif


BIN
uis/layuiadmin/layui/images/face/52.gif


BIN
uis/layuiadmin/layui/images/face/53.gif


BIN
uis/layuiadmin/layui/images/face/54.gif


BIN
uis/layuiadmin/layui/images/face/55.gif


BIN
uis/layuiadmin/layui/images/face/56.gif


BIN
uis/layuiadmin/layui/images/face/57.gif


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff