wushaodong 4 жил өмнө
commit
4729ebb9c6
100 өөрчлөгдсөн 2010 нэмэгдсэн , 0 устгасан
  1. 15 0
      .gitignore
  2. 89 0
      apps/Alipay.py
  3. 228 0
      apps/WeChatResponse.py
  4. 0 0
      apps/WechatApplet/__init__.py
  5. 17 0
      apps/WechatApplet/filters.py
  6. 0 0
      apps/WechatApplet/migrations/__init__.py
  7. 96 0
      apps/WechatApplet/models.py
  8. 0 0
      apps/WechatTp/__init__.py
  9. 0 0
      apps/WechatTp/migrations/__init__.py
  10. 84 0
      apps/WechatTp/models.py
  11. 0 0
      apps/__init__.py
  12. 1 0
      apps/account/__init__.py
  13. 23 0
      apps/account/filters.py
  14. 0 0
      apps/account/migrations/__init__.py
  15. 116 0
      apps/account/models.py
  16. 45 0
      apps/account/serializers.py
  17. 9 0
      apps/account/urls.py
  18. 28 0
      apps/account/views.py
  19. 0 0
      apps/agent/__init__.py
  20. 0 0
      apps/agent/migrations/__init__.py
  21. 53 0
      apps/agent/models.py
  22. 12 0
      apps/agent/serializers.py
  23. 0 0
      apps/api/__init__.py
  24. 11 0
      apps/api/urls.py
  25. 152 0
      apps/api/views.py
  26. 157 0
      apps/base.py
  27. 0 0
      apps/dashboard/__init__.py
  28. 11 0
      apps/dashboard/views.py
  29. 0 0
      apps/log/__init__.py
  30. 20 0
      apps/log/filters.py
  31. 0 0
      apps/log/migrations/__init__.py
  32. 54 0
      apps/log/models.py
  33. 184 0
      apps/serializer_errors.py
  34. 0 0
      apps/upload/__init__.py
  35. 0 0
      apps/upload/migrations/__init__.py
  36. 131 0
      apps/upload/models.py
  37. 12 0
      apps/upload/serializers.py
  38. 0 0
      decorate/__init__.py
  39. 254 0
      decorate/settings.py
  40. 32 0
      decorate/urls.py
  41. 16 0
      decorate/wsgi.py
  42. 21 0
      manage.py
  43. 13 0
      requirements
  44. BIN
      static/imgs/aliPay.png
  45. BIN
      static/imgs/wechart.png
  46. BIN
      static/xls/设备信息导入模板.xlsx
  47. 95 0
      uis/layuiadmin/config.js
  48. 1 0
      uis/layuiadmin/layui/css/layui.css
  49. 1 0
      uis/layuiadmin/layui/css/layui.mobile.css
  50. 2 0
      uis/layuiadmin/layui/css/modules/code.css
  51. 1 0
      uis/layuiadmin/layui/css/modules/laydate/default/laydate.css
  52. BIN
      uis/layuiadmin/layui/css/modules/layer/default/icon-ext.png
  53. BIN
      uis/layuiadmin/layui/css/modules/layer/default/icon.png
  54. 1 0
      uis/layuiadmin/layui/css/modules/layer/default/layer.css
  55. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-0.gif
  56. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-1.gif
  57. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-2.gif
  58. BIN
      uis/layuiadmin/layui/font/iconfont.eot
  59. 25 0
      uis/layuiadmin/layui/font/iconfont.svg
  60. BIN
      uis/layuiadmin/layui/font/iconfont.ttf
  61. BIN
      uis/layuiadmin/layui/font/iconfont.woff
  62. BIN
      uis/layuiadmin/layui/images/face/0.gif
  63. BIN
      uis/layuiadmin/layui/images/face/1.gif
  64. BIN
      uis/layuiadmin/layui/images/face/10.gif
  65. BIN
      uis/layuiadmin/layui/images/face/11.gif
  66. BIN
      uis/layuiadmin/layui/images/face/12.gif
  67. BIN
      uis/layuiadmin/layui/images/face/13.gif
  68. BIN
      uis/layuiadmin/layui/images/face/14.gif
  69. BIN
      uis/layuiadmin/layui/images/face/15.gif
  70. BIN
      uis/layuiadmin/layui/images/face/16.gif
  71. BIN
      uis/layuiadmin/layui/images/face/17.gif
  72. BIN
      uis/layuiadmin/layui/images/face/18.gif
  73. BIN
      uis/layuiadmin/layui/images/face/19.gif
  74. BIN
      uis/layuiadmin/layui/images/face/2.gif
  75. BIN
      uis/layuiadmin/layui/images/face/20.gif
  76. BIN
      uis/layuiadmin/layui/images/face/21.gif
  77. BIN
      uis/layuiadmin/layui/images/face/22.gif
  78. BIN
      uis/layuiadmin/layui/images/face/23.gif
  79. BIN
      uis/layuiadmin/layui/images/face/24.gif
  80. BIN
      uis/layuiadmin/layui/images/face/25.gif
  81. BIN
      uis/layuiadmin/layui/images/face/26.gif
  82. BIN
      uis/layuiadmin/layui/images/face/27.gif
  83. BIN
      uis/layuiadmin/layui/images/face/28.gif
  84. BIN
      uis/layuiadmin/layui/images/face/29.gif
  85. BIN
      uis/layuiadmin/layui/images/face/3.gif
  86. BIN
      uis/layuiadmin/layui/images/face/30.gif
  87. BIN
      uis/layuiadmin/layui/images/face/31.gif
  88. BIN
      uis/layuiadmin/layui/images/face/32.gif
  89. BIN
      uis/layuiadmin/layui/images/face/33.gif
  90. BIN
      uis/layuiadmin/layui/images/face/34.gif
  91. BIN
      uis/layuiadmin/layui/images/face/35.gif
  92. BIN
      uis/layuiadmin/layui/images/face/36.gif
  93. BIN
      uis/layuiadmin/layui/images/face/37.gif
  94. BIN
      uis/layuiadmin/layui/images/face/38.gif
  95. BIN
      uis/layuiadmin/layui/images/face/39.gif
  96. BIN
      uis/layuiadmin/layui/images/face/4.gif
  97. BIN
      uis/layuiadmin/layui/images/face/40.gif
  98. BIN
      uis/layuiadmin/layui/images/face/41.gif
  99. BIN
      uis/layuiadmin/layui/images/face/42.gif
  100. BIN
      uis/layuiadmin/layui/images/face/43.gif

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+# 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
+python*
+uis/up*
+.history

+ 89 - 0
apps/Alipay.py

@@ -0,0 +1,89 @@
+#coding=utf-8
+import traceback
+from django.conf import settings
+from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
+from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
+from alipay.aop.api.domain.AlipayTradePrecreateModel import AlipayTradePrecreateModel
+from alipay.aop.api.domain.AlipayTradeQueryModel import AlipayTradeQueryModel
+from alipay.aop.api.request.AlipayTradePrecreateRequest import AlipayTradePrecreateRequest
+from alipay.aop.api.request.AlipayTradeQueryRequest import AlipayTradeQueryRequest
+from alipay.aop.api.response.AlipayTradePrecreateResponse import AlipayTradePrecreateResponse
+from alipay.aop.api.response.AlipayTradeQueryResponse import AlipayTradeQueryResponse
+
+from utils.exceptions import CustomError
+
+class Alipay():
+    @staticmethod
+    def payUnifiedOrder(payment_no,amount):
+        if amount < 0.01:
+            raise CustomError(u'无效的金额')
+
+        alipay_client_config = AlipayClientConfig()
+
+        try:
+            alipay_client_config.app_id = settings.ALIPAY_SETTING['app_id']
+            alipay_client_config.app_private_key = settings.ALIPAY_SETTING['app_private_key']
+            alipay_client_config.alipay_public_key  = settings.ALIPAY_SETTING['alipay_public_key']
+            notify_url = settings.ALIPAY_SETTING['notify_url']
+        except:
+            raise CustomError(u'没有配置支付宝对接信息')
+
+        alipay_client_config.timeout = 120
+        client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
+
+        model = AlipayTradePrecreateModel()
+        model.out_trade_no = payment_no
+        model.total_amount = amount
+        model.subject = settings.PAY_SUBJECT
+
+        request = AlipayTradePrecreateRequest(biz_model=model)
+        request.notify_url = notify_url
+
+        try:
+            response_content = client.execute(request)
+        except Exception as e:
+            traceback.print_exc()
+            raise CustomError(str(e))
+        if not response_content:
+            raise CustomError(u'支付宝请求失败!')
+        response = AlipayTradePrecreateResponse()
+        response.parse_response_content(response_content)
+        if not response.is_success():
+            raise CustomError(response.code + "," + response.msg + "," + response.sub_code + "," + response.sub_msg)
+        return response.qr_code
+
+    @staticmethod
+    def queryUnifiedOrder(payment_no):
+        if not payment_no:
+            raise CustomError(u'无效的支付单号')
+
+        alipay_client_config = AlipayClientConfig()
+
+        try:
+            alipay_client_config.app_id = settings.ALIPAY_SETTING['app_id']
+            alipay_client_config.app_private_key = settings.ALIPAY_SETTING['app_private_key']
+            alipay_client_config.alipay_public_key  = settings.ALIPAY_SETTING['alipay_public_key']
+        except:
+            raise CustomError(u'没有配置支付宝对接信息')
+
+        alipay_client_config.timeout = 120
+        client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
+
+        model = AlipayTradeQueryModel()
+        model.out_trade_no = payment_no
+
+        request = AlipayTradeQueryRequest(biz_model=model)
+
+        try:
+            response_content = client.execute(request)
+        except Exception as e:
+            traceback.print_exc()
+            raise CustomError(str(e))
+        if not response_content:
+            raise CustomError(u'支付宝请求失败!')
+        response = AlipayTradeQueryResponse()
+        response.parse_response_content(response_content)
+        if not response.is_success():
+            raise CustomError(response.code + "," + response.msg + "," + response.sub_code + "," + response.sub_msg)
+        return response.buyer_pay_amount
+

+ 228 - 0
apps/WeChatResponse.py

@@ -0,0 +1,228 @@
+# coding=utf-8
+
+import uuid
+import requests
+import json
+import xmltodict
+import time
+from hashlib import md5
+from django.conf import settings
+
+from utils.exceptions import CustomError
+
+# 微信支付APP_ID
+WEIXIN_APP_ID = settings.WECHAT['appid']
+# 微信支付MCH_ID 【登录账号】
+WEIXIN_MCH_ID = settings.WECHAT['mchid']
+# 微信支付sign_type
+WEIXIN_SIGN_TYPE = 'MD5'
+# 服务器IP地址
+WEIXIN_SPBILL_CREATE_IP = settings.WECHAT['spbill_create_ip']
+# 微信支付用途
+WEIXIN_BODY = settings.PAY_SUBJECT
+# 微信KEY值 【API密钥】
+WEIXIN_KEY = settings.WECHAT['merchant_key']
+# 微信统一下单URL
+WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
+
+# 微信查询订单URL
+WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
+
+# 微信支付回调API
+WEIXIN_CALLBACK_API = settings.WECHAT['notify_url']
+
+# pc端支付
+class WeChatResponse():
+
+    def __init__(self):
+        self.params = {
+            'appid': WEIXIN_APP_ID,
+            'mch_id': WEIXIN_MCH_ID,
+            'nonce_str': '',
+            'sign_type': WEIXIN_SIGN_TYPE,
+            'sign': '',
+            'out_trade_no': '',
+        }
+        self.prepay_id = None
+
+    def getAppString(self):
+        data = {
+            'appid': self.params['appid'],
+            'partnerid': self.params['mch_id'],
+            'prepayid': self.prepay_id,
+            'package': "prepay_id={}".format(self.prepay_id),
+            'noncestr': generate_nonce_str(),
+            'timestamp': str(int(time.time()))
+        }
+        data['sign'] = generate_sign(data)
+        return data
+
+    # 查询订单
+    def orderquery(self, out_trade_no):
+        self.params['out_trade_no'] = out_trade_no
+        self.params['nonce_str'] = generate_nonce_str()
+        self.params['sign'] = generate_sign(self.params)
+        data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
+        headers = {'Content-Type': 'application/xml'}
+        res = requests.post(WEIXIN_QUERY_ORDER_URL, data=data, headers=headers)
+        if res.status_code != 200:
+            raise CustomError(u'微信请求失败!')
+        result = json.loads(json.dumps(xmltodict.parse(res.content)))
+
+        if result['xml']['return_code'] != 'SUCCESS':
+            raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
+        if result['xml']['trade_state'] != 'SUCCESS':
+            raise CustomError(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
+
+        return result['xml']['total_fee']
+
+    def unifiedOrder(self, out_trade_no, total_fee):
+        self.params['out_trade_no'] = out_trade_no
+        self.params['total_fee'] = int(round(total_fee * 100, 0))
+        self.params['nonce_str'] = generate_nonce_str()
+        self.params['body'] = WEIXIN_BODY
+        self.params['spbill_create_ip'] = WEIXIN_SPBILL_CREATE_IP
+        self.params['notify_url'] = WEIXIN_CALLBACK_API
+        self.params['trade_type'] = 'NATIVE'
+        self.params['sign'] = generate_sign(self.params)
+        data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
+
+        headers = {'Content-Type': 'application/xml'}
+        res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=data, headers=headers)
+
+        if res.status_code != 200:
+            raise CustomError(u'微信请求失败!')
+
+        result = json.loads(json.dumps(xmltodict.parse(res.content)))
+        if result['xml']['return_code'] != 'SUCCESS':
+            raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
+        if result['xml']['result_code'] != 'SUCCESS':
+            raise CustomError(u'微信交易失败![%s:%s]' % (result['xml']['err_code'], result['xml']['err_code_des']))
+
+        self.prepay_id = result['xml']['prepay_id']
+        return result['xml']['code_url']
+
+#小程序支付
+class WechatAppletPay():
+
+    def __init__(self):
+        self.params = {
+            "appid":  settings.WEAPP['appid'],
+            "body": WEIXIN_BODY,
+            "mch_id": WEIXIN_MCH_ID,
+            "nonce_str": '',
+            "notify_url": WEIXIN_CALLBACK_API,
+            "openid": '',  # 获取小程序openid
+            "out_trade_no": '',
+            "spbill_create_ip": WEIXIN_SPBILL_CREATE_IP,
+            "total_fee": '',
+            "trade_type": 'JSAPI',
+            "sign":''
+        }
+        self.prepay_id = None
+
+    def weChatUnifiedOrder(self, openid, out_trade_no, total_fee):
+        self.params['out_trade_no'] = out_trade_no
+        self.params['total_fee'] = int(round(float(total_fee) * 100, 0))
+        self.params['nonce_str'] = generate_nonce_str()
+        self.params['openid'] = openid
+        self.params['sign'] = generate_sign(self.params)
+        # 拿到封装好的xml数据
+        xml_data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
+        # 请求微信统一下单URL
+        headers = {'Content-Type': 'application/xml'}
+        res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=xml_data, headers=headers)
+
+        if res.status_code != 200:
+            raise CustomError(u'微信请求失败!')
+
+        # 回复数据为xml,将其转为字典
+        content = json.loads(json.dumps(xmltodict.parse(res.content)))
+
+        if content['xml']['return_code'] != 'SUCCESS':
+            raise CustomError(u'微信通信失败![%s]' % content['xml']['return_msg'])
+        if content['xml']['result_code'] != 'SUCCESS':
+            raise CustomError(u'微信交易失败![%s:%s]' % (content['xml']['err_code'],content['xml']['err_code_des']))
+
+        self.prepay_id = content['xml']['prepay_id']
+
+        return self.getPaymentInfo()
+
+    def getPaymentInfo(self):
+        data = {
+            'appId': self.params['appid'],
+            'package': "prepay_id={}".format(self.prepay_id),
+            'nonceStr': generate_nonce_str(),
+            'timeStamp': str(int(time.time())),
+            'signType':'MD5'
+        }
+        data['paySign'] = generate_sign(data)
+        return data
+
+class WechatPayNotify():
+    def __init__(self, params):
+        self.params = params
+
+    def handle(self):
+        resp_dict = json.loads(json.dumps(xmltodict.parse(self.params)))['xml']
+        return_code = resp_dict['return_code']
+
+        if return_code != 'SUCCESS':
+            return None
+
+        if not validate_sign(resp_dict):
+            return None
+
+        return resp_dict
+
+    @staticmethod
+    def response_ok():
+        return_info = {
+            'return_code': 'SUCCESS',
+            'return_msg': 'OK'
+        }
+        return generate_response_data(return_info)
+
+    @staticmethod
+    def response_fail():
+        return_info = {
+            'return_code': 'FAIL',
+            'return_msg': 'FAIL'
+        }
+        return generate_response_data(return_info)
+
+def generate_nonce_str():
+    """
+    生成随机字符串
+    """
+    return str(uuid.uuid4()).replace('-', '')
+
+
+def generate_sign(params):
+    """
+    生成md5签名的参数
+    """
+    if 'sign' in params:
+        params.pop('sign')
+    src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
+    return md5(src.encode('utf-8')).hexdigest().upper()
+
+
+def validate_sign(resp_dict):
+    """
+    验证微信返回的签名
+    """
+    if 'sign' not in resp_dict:
+        return False
+    wx_sign = resp_dict['sign']
+    sign = generate_sign(resp_dict)
+    if sign == wx_sign:
+        return True
+    return False
+
+
+def generate_response_data(resp_dict):
+    """
+    字典转xml
+    """
+    return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')

+ 0 - 0
apps/WechatApplet/__init__.py


+ 17 - 0
apps/WechatApplet/filters.py

@@ -0,0 +1,17 @@
+# coding=utf-8
+
+import django_filters
+
+from apps.WechatApplet.models import WechatApplet
+
+
+class WechatAppletFilter(django_filters.FilterSet):
+    tenant_name = django_filters.CharFilter(field_name='tenant__name', lookup_expr='icontains')
+    user_version = django_filters.CharFilter(field_name='user_version', lookup_expr='icontains')
+    wait_audit_version = django_filters.CharFilter(field_name='wait_audit_version', lookup_expr='icontains')
+    wait_audit_template = django_filters.CharFilter(field_name='wait_audit_template', lookup_expr='icontains')
+    audit_status = django_filters.CharFilter(field_name='audit_status')
+
+    class Meta:
+        model = WechatApplet
+        fields = '__all__'

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


+ 96 - 0
apps/WechatApplet/models.py

@@ -0,0 +1,96 @@
+# coding=utf-8
+
+import time
+import os
+from django.conf import settings
+from django.db import models
+from django.utils import timezone
+from apps.WechatTp.models import WechatTp
+from utils.wx.wechat import WeChat
+from utils.exceptions import CustomError
+
+class WechatApplet(models.Model):
+
+    authorizer_appid = models.CharField(max_length=512, verbose_name=u'授权方appid')
+    secret = models.CharField(max_length=512, verbose_name=u'小程序秘钥', null=True)
+    authorizer_refresh_token = models.CharField(max_length=512, verbose_name=u'刷新令牌')
+    authorizer_access_token = models.CharField(max_length=512, verbose_name=u'令牌')
+    access_token_gtime = models.DateTimeField(verbose_name=u"获取令牌时间")
+    expires_in = models.IntegerField(verbose_name=u'令牌有效期')
+
+    class Meta:
+        db_table = "wechat_applet"
+        ordering = ['-id']
+        index_together = ()
+        verbose_name = u"小程序"
+        default_permissions = ()
+
+    def getAccessToken(self):
+        if self.authorizer_access_token:
+            last_time = time.mktime(self.access_token_gtime.timetuple()) + self.expires_in
+            now = time.mktime(timezone.now().timetuple())
+            if last_time > now:
+                return self.authorizer_access_token
+        gtime = timezone.now()
+        res = WeChat.getAccessToken(self.authorizer_appid, self.secret)
+        self.refreshAccessToken(gtime, res['access_token'], res['expires_in'])
+        return self.authorizer_access_token
+
+    def refreshAccessToken(self, gtime, access_token, expires_in):
+        self.authorizer_access_token = access_token
+        self.access_token_gtime = gtime
+        self.expires_in = expires_in
+        self.save()
+
+    def sendFinishMsg(self, openid, name, address, fault_des, no):
+        template_id = settings.WEAPP['message_template_finish']
+        if not template_id:
+            return
+        time = timezone.now().strftime('%Y-%m-%d %H:%M:%S')
+        data = {
+            'time5': {'value': time},
+            'name4':{'value':name},
+            'thing3':{'value':address},
+            'thing2':{'value': fault_des},
+            'character_string7':{'value': no},
+        }
+        page = 'pages/repairList/repairList?sort=yiwangong&name=已完工'
+        WeChat.sendSubscribeMessage(self.getAccessToken(), openid, template_id, page, data)
+
+    def sendWaitCheckMsg(self, openid, name, address, fault_des, no):
+        template_id = settings.WEAPP['message_template_wait_check']
+        if not template_id:
+            return
+        data = {
+            'character_string12': {'value': no},
+            'thing1':{'value':'新报修审核'},
+            'thing19':{'value':fault_des},
+            'name3':{'value': name},
+            'thing10':{'value': address},
+        }
+        page = '/pages/certificate/certificate'
+        WeChat.sendSubscribeMessage(self.getAccessToken(), openid, template_id, page, data)
+
+    def sendDispatchMsg(self, openid, name, address, fault_des, time, no):
+        template_id = settings.WEAPP['message_template_dispatch']
+        if not template_id:
+            return
+        data = {
+            'character_string1': {'value': no},
+            'thing3':{'value':fault_des},
+            'time5':{'value':time.strftime('%Y-%m-%d %H:%M:%S')},
+            'thing6':{'value': name},
+            'thing8':{'value': address},
+        }
+        page = '/pages/repairList/repairList?sort=wodepaidan&name=我的派单'
+        WeChat.sendSubscribeMessage(self.getAccessToken(), openid, template_id, page, data)
+
+    def getWXAppCode(self, company_no):
+        page = 'pages/index/index'
+        filename = WeChat.getWXAppCode(self.getAccessToken(),page, company_no)
+        return filename
+
+    def getDeviceCode(self, device_id, company_no):
+        page = 'pages/index/index'
+        filename = WeChat.getDeviceCode(self.getAccessToken(),page, device_id, company_no)
+        return filename

+ 0 - 0
apps/WechatTp/__init__.py


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


+ 84 - 0
apps/WechatTp/models.py

@@ -0,0 +1,84 @@
+# coding=utf-8
+
+import time
+from django.db import models
+from django.utils import timezone
+
+from utils.exceptions import CustomError
+
+from utils.wx.wechat import WeChat
+
+
+class WechatTp(models.Model):
+    component_appid = models.CharField(max_length=512, verbose_name=u'第三方平台appid')
+    component_appsecret = models.CharField(max_length=512, verbose_name=u'第三方平台appsecret')
+    m_encode_key = models.CharField(max_length=512, verbose_name=u'消息加密key')
+    m_token = models.CharField(max_length=512, verbose_name=u'消息校验token')
+
+    component_verify_ticket = models.CharField(max_length=512, verbose_name=u'验证票据', null=True)
+    component_access_token = models.CharField(max_length=512, verbose_name=u'令牌', null=True)
+    access_token_gtime = models.DateTimeField(verbose_name=u"获取令牌时间", null=True)
+    expires_in = models.IntegerField(verbose_name=u'令牌有效期', null=True)
+
+    class Meta:
+        db_table = "wechat_tp"
+        ordering = ['-id']
+        index_together = ()
+        verbose_name = u"第三方平台"
+        default_permissions = ()
+
+    @staticmethod
+    def getDefault():
+        tp = WechatTp.objects.filter().first()
+        if not tp:
+            raise CustomError(u'未找到微信第三方平台!')
+        return tp
+
+    @staticmethod
+    def getByAppid(appid):
+        component = WechatTp.objects.filter(component_appid=appid).first()
+        if not component:
+            raise CustomError(u'未找到相应的第三方平台!')
+        return component
+
+    def getAppid(self):
+        return self.component_appid
+
+    def getToken(self):
+        return self.m_token
+
+    def getEncodeKey(self):
+        return self.m_encode_key
+
+    def getAccessToken(self):
+        if self.component_access_token:
+            last_time = time.mktime(self.access_token_gtime.timetuple()) + self.expires_in
+            now = time.mktime(timezone.now().timetuple())
+            if last_time > now:
+                return self.component_access_token
+        res = WeChat.getComponentAccessToken(self.component_appid, self.component_appsecret, self.component_verify_ticket)
+        self.refreshAccessToken(timezone.now(), res['component_access_token'], res['expires_in'])
+        return self.component_access_token
+
+    def getPreAuthCode(self):
+        res = WeChat.getPreAuthCode(self.component_appid, self.getAccessToken())
+        return res['pre_auth_code']
+
+    def refreshVerifyTicket(self, component_verify_ticket):
+        self.component_verify_ticket = component_verify_ticket
+        self.save()
+
+    def refreshAccessToken(self, gtime, component_access_token, expires_in):
+        self.access_token_gtime = gtime
+        self.component_access_token = component_access_token
+        self.expires_in = expires_in
+        self.save()
+
+    def getAuthUrl(self, tenant_id):
+        url = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=' + self.getAppid() + '&pre_auth_code=' + self.getPreAuthCode()
+        url += '&redirect_uri=https://baoxiu360.top/api/redirect_authorize/' + tenant_id + '/&auth_type=2'
+        return url
+
+    def getTemplateList(self):
+        template_list = WeChat.getCodeTemplateList(self.getAccessToken())
+        return template_list

+ 0 - 0
apps/__init__.py


+ 1 - 0
apps/account/__init__.py

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

+ 23 - 0
apps/account/filters.py

@@ -0,0 +1,23 @@
+# coding=utf-8
+import django_filters
+
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
+
+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']
+
+
+class GroupFilter(django_filters.FilterSet):
+
+    class Meta:
+        model = Group
+        fields = '__all__'

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


+ 116 - 0
apps/account/models.py

@@ -0,0 +1,116 @@
+# coding=utf-8
+
+from django.db import models
+from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager, Group
+from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager, Group, AbstractUser
+from django.utils import timezone
+from rest_framework.utils import model_meta
+from utils.exceptions import CustomError
+from django.conf import settings
+from apps.agent.models import Store
+
+class UserManager(BaseUserManager):
+
+    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):
+
+    WAITING_JOIN = 1
+    INSERVICE = 2
+    DIMISSION = 3
+
+    STATUS_CHOICES = (
+        (WAITING_JOIN, u'待入职'),
+        (INSERVICE, u'在职'),
+        (DIMISSION, u'离职'),
+    )
+    username = models.CharField(verbose_name=u'用户名', max_length=30, unique=True, db_index=True,
+                                help_text=u'不多于20个字符。只能用字母、数字和字符。')
+
+    is_active = models.BooleanField(verbose_name=u'是否可用', default=True, editable=False)
+    date_joined = models.DateTimeField(verbose_name=u'注册时间', auto_now_add=True, editable=False)
+
+    name = models.CharField(max_length=20, verbose_name=u"姓名")
+    gender = models.PositiveSmallIntegerField(choices=settings.GENDER_CHOICES, verbose_name=u"性别")
+    ID_card = models.CharField(max_length=18, verbose_name=u"身份证号", null=True, blank=True)
+    address = models.CharField(max_length=100, verbose_name=u"家庭住址", null=True, blank=True)
+    tel = models.CharField(max_length=15, verbose_name=u"手机")
+    store = models.ForeignKey(Store, verbose_name=u"所属门店", null=True, blank=True, on_delete=models.PROTECT)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"是否在职", default=INSERVICE)
+    check_user = models.ForeignKey('User', verbose_name=u'审核人', related_name='user_check_user', null=True,
+                                   on_delete=models.PROTECT)
+    check_time = models.DateTimeField(verbose_name=u'审核时间', editable=False, null=True)
+
+    objects = UserManager()
+
+    USERNAME_FIELD = 'username'
+    REQUIRED_FIELDS = []
+
+    class Meta:
+        db_table = "auth_user"
+        verbose_name = u"权限管理"
+        unique_together = [
+            ('username')
+        ]
+        ordering = ['-id']
+        default_permissions = ()
+        permissions = [
+            ('browse_user', u'查看'),
+            ('add_user', u'添加'),
+            ('delete_user', u'删除'),
+        ]
+
+    def __unicode__(self):
+        return self.username
+
+    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
+
+
+Group.add_to_class('display_name', models.CharField(verbose_name=u'名称显示', max_length=80))
+Group.add_to_class('create_user', models.ForeignKey(User, verbose_name=u"创建人", on_delete=models.PROTECT, editable=False))

+ 45 - 0
apps/account/serializers.py

@@ -0,0 +1,45 @@
+# coding=utf-8
+
+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.log.models import BizLog
+from utils import get_remote_addr
+
+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_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)

+ 9 - 0
apps/account/urls.py

@@ -0,0 +1,9 @@
+from django.conf.urls import url
+
+from apps.account.views import *
+
+urlpatterns = (
+    url(r'^login/$', LoginView.as_view()),
+    url(r'^token_refresh/$', RefreshTokenView.as_view()),
+)
+

+ 28 - 0
apps/account/views.py

@@ -0,0 +1,28 @@
+#coding=utf-8
+
+from rest_framework.serializers import ValidationError
+
+from rest_framework_jwt.views import ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken
+from utils import response_error, response_ok
+from apps.account.serializers import JWTSerializer
+
+class LoginView(ObtainJSONWebToken):
+    serializer_class = JWTSerializer
+
+    def post(self, request, *args, **kwargs):
+        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])
+
+class RefreshTokenView(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'登录状态失效,请重新登录')

+ 0 - 0
apps/agent/__init__.py


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


+ 53 - 0
apps/agent/models.py

@@ -0,0 +1,53 @@
+# coding=utf-8
+import os
+
+from PIL import Image
+from django.conf import settings
+from django.db import models
+from utils.file_operation import UploadFile, DeleteFile
+from django.utils import timezone
+
+class Agent(models.Model):
+    name = models.CharField(verbose_name=u'名称', max_length=50)
+    tel = models.CharField(verbose_name=u'电话', max_length=20, null=True)
+    area = models.CharField(verbose_name=u'区域', max_length=50)
+    address = models.CharField(verbose_name=u'地址', max_length=200)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'添加人', on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u'添加时间', auto_now_add=True, editable=False)
+
+    class Meta:
+        db_table = 'agent'
+        verbose_name = u'代理商'
+        ordering = ['-id']
+        default_permissions = ()
+        permissions = [
+            ('view_agent', u'查看'),
+            ('add_agent', u'添加'),
+            ('delete_agent', u'删除'),
+        ]
+
+class Store(models.Model):
+
+    name = models.CharField(verbose_name=u'名称', max_length=50)
+    address = models.CharField(verbose_name=u'地址', max_length=200)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL,related_name='store_create_user', verbose_name=u'添加人', on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u'添加时间', auto_now_add=True, editable=False)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATE_CHOICES, verbose_name=u"状态", default=settings.DEFAULT)
+    enable = models.BooleanField(verbose_name=u"是否在用", default=True)
+    end_date = models.DateField(verbose_name=u'到期日期', null=True)
+    notes = models.CharField(verbose_name=u'备注', max_length=200, null=True)
+    agent = models.ForeignKey(Agent, verbose_name=u'代理商', on_delete=models.PROTECT)
+    check_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'审核人',related_name='store_check_user', on_delete=models.PROTECT)
+    check_time = models.DateTimeField(verbose_name=u'审核时间', editable=False, null=True)
+
+    class Meta:
+        db_table = 'store'
+        verbose_name = u'门店'
+        ordering = ['-id']
+        default_permissions = ()
+        permissions = [
+            ('view_store', u'查看'),
+            ('add_store', u'添加'),
+            ('delete_store', u'删除'),
+            ('check_store', u'审核'),
+        ]

+ 12 - 0
apps/agent/serializers.py

@@ -0,0 +1,12 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from django.conf import settings
+from .models import Upload
+
+
+class UploadSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Upload
+        fields = ('picture', 'width', 'height', 'type', 'voice_time')

+ 0 - 0
apps/api/__init__.py


+ 11 - 0
apps/api/urls.py

@@ -0,0 +1,11 @@
+# coding=utf-8
+
+from django.conf.urls import url
+from .views import *
+urlpatterns = [
+    url(r'^callback_authorize/$', CallbackAuthorize.as_view()),
+    url('^(?P<appid>.+)/callback_event/$', CallbackEvent.as_view()),
+
+    url(r'^alipay_notify/$', AlipayNotifyView.as_view()),
+    url(r'^wechat_notify/$', WechatNotifyView.as_view()),
+]

+ 152 - 0
apps/api/views.py

@@ -0,0 +1,152 @@
+# coding=utf-8
+
+import json
+import xmltodict
+import traceback
+from django.db import transaction
+from django.http import HttpResponse
+from rest_framework.views import APIView
+from django.conf import settings
+from utils.wx.WXBizMsgCrypt import WXBizMsgCrypt
+from utils.exceptions import CustomError
+from utils import response_ok, response_error
+from utils.wechatpay import WechatPayNotify
+
+from apps.log.models import BizLog
+from apps.WechatTp.models import WechatTp
+from apps.WechatApplet.models import WechatApplet
+from alipay.aop.api.util.SignatureUtils import verify_with_rsa
+
+from apps.WeChatResponse import WechatPayNotify
+
+class CallbackAuthorize(APIView):
+    '''验证票据(component_verify_ticket)在第三方平台创建审核通过后,微信服务器会向其 ”授权事件接收URL”
+    每隔 10 分钟以 POST 的方式推送 component_verify_ticket 接收 POST 请求后,只需直接返回字符串 success。
+    为了加强安全性,postdata 中的 xml 将使用服务申请时的加解密 key 来进行加密'''
+
+    def post(self, request):
+        sMsgSignature = request.GET.get('msg_signature')
+        sTimeStamp = request.GET.get('timestamp')
+        sNonce = request.GET.get('nonce')
+        sPostData = request.body.decode('utf-8')
+
+        try:
+            component = WechatTp.getDefault()
+            if component:
+                appid = component.getAppid()
+                msg_crypt = WXBizMsgCrypt(component.getToken(), component.getEncodeKey(), appid)
+                ret, decryp_xml = msg_crypt.DecryptMsg(sPostData, sMsgSignature, sTimeStamp, sNonce)
+                data = json.loads(json.dumps(xmltodict.parse(decryp_xml)))['xml']
+                if data['AppId'] == appid:
+                    if data['InfoType'] == 'component_verify_ticket':
+                        component.refreshVerifyTicket(data['ComponentVerifyTicket'])
+                    elif data['InfoType'] == 'unauthorized':
+                        authorizer_appid = data['AuthorizerAppid']
+                        authorizer = WechatApplet.getByAppidAndComponentAppid(authorizer_appid, appid)
+                        if authorizer:
+                            authorizer.revoke()
+        except:
+            pass
+
+        return HttpResponse('success')
+
+
+class CallbackEvent(APIView):
+    '''消息与事件接收'''
+
+    def post(self, request, appid):
+        appid = appid
+        sMsgSignature = request.GET.get('msg_signature')
+        sTimeStamp = request.GET.get('timestamp')
+        sNonce = request.GET.get('nonce')
+        sPostData = request.body.decode('utf-8')
+
+        try:
+            tp = WechatTp.getDefault()
+            msg_crypt = WXBizMsgCrypt(tp.getToken(), tp.getEncodeKey(), tp.getAppid())
+            ret, decryp_xml = msg_crypt.DecryptMsg(sPostData, sMsgSignature, sTimeStamp, sNonce)
+            data = json.loads(json.dumps(xmltodict.parse(decryp_xml)))['xml']
+            if data['MsgType'] == 'event':
+                app = WechatApplet.getByAppid(appid)
+                if data['Event'] == 'weapp_audit_success':  # 代码审核通过
+                    app.weapp_audit_success()
+                elif data['Event'] == 'weapp_audit_fail':  # 代码审核不通过
+                    app.weapp_audit_fail(data['Reason'])
+                elif data['Event'] == 'weapp_audit_delay':  # 代码审核延后
+                    app.weapp_audit_delay(data['Reason'])
+        except:
+            pass
+        return HttpResponse('success')
+
+class AlipayNotifyView(APIView):
+    def check_pay(self, params):
+        sign = params.pop('sign', None)
+        params.pop('sign_type')
+        params = sorted(params.items(), key=lambda e: e[0], reverse=False)
+        message = "&".join(u"{}={}".format(k, v) for k, v in params).encode()
+        try:
+            status = verify_with_rsa(settings.ALIPAY_SETTING['alipay_public_key'].encode('utf-8').decode('utf-8'), message,sign)
+            return status
+        except:
+            return False
+
+    def post(self, request):
+        params = request.POST.dict()
+        if not self.check_pay(params):
+            return HttpResponse('')
+
+
+        no = request.POST.get('out_trade_no')
+        app_id = request.POST.get('app_id')
+        notify_type = request.POST.get('notify_type')
+        trade_status = request.POST.get('trade_status')
+        amount = request.POST.get('total_amount')
+
+        if notify_type != 'trade_status_sync':
+            return HttpResponse('')
+
+        if app_id != settings.ALIPAY_SETTING['app_id']:
+            return HttpResponse('')
+
+        try:
+            with transaction.atomic():
+                pay = Pay.getByNo(no)
+                if trade_status == 'TRADE_CLOSED':
+                    pay.payClosed()
+                    BizLog.objects.addnew(None,pay.user, BizLog.INSERT, u'支付取消,pay_no=%s' % no,params)
+
+                if trade_status == 'TRADE_SUCCESS' or trade_status == 'TRADE_FINISHED':
+                    pay.paySuccess(float(amount))
+                    BizLog.objects.addnew(None,pay.user, BizLog.INSERT, u'支付宝支付成功,pay_no=%s' % no,params)
+        except:
+            import traceback
+            traceback.print_exc()
+            return HttpResponse('')
+        return HttpResponse('success')
+
+
+class WechatNotifyView(APIView):
+
+    def dispatch(self, request, *args, **kwargs):
+        param = request.body
+        notify = WechatPayNotify(param)
+        try:
+            data = notify.handle()
+            if not data:
+                raise CustomError(u'错误的请求!')
+
+            result_code = data['result_code']
+            if result_code != 'SUCCESS':
+                raise CustomError(u'错误的请求!')
+
+            no = data['out_trade_no']
+            amount = float(data['total_fee']) / 100.0
+
+            with transaction.atomic():
+                pay = Pay.getByNo(no)
+                pay.paySuccess(amount)
+                BizLog.objects.addnew(None, pay.user, BizLog.INSERT, u'微信支付成功,pay_no=%s' % no, param)
+        except Exception as e:
+            traceback.print_exc()
+            return HttpResponse(WechatPayNotify.response_fail())
+        return HttpResponse(WechatPayNotify.response_ok())

+ 157 - 0
apps/base.py

@@ -0,0 +1,157 @@
+#coding=utf-8
+from utils.exceptions import CustomError
+import re
+import tablib
+import datetime
+from openpyxl.utils.exceptions import InvalidFileException
+
+class Formater():
+    @staticmethod
+    def formatStr(value):
+        res = u''
+        if value != None:
+            try:
+                res = str(value)
+            except:
+                pass
+        return res
+
+    @staticmethod
+    def formatCount(value):
+        return int(round(float(value or 0) * 100,0))
+
+    @staticmethod
+    def formatPrice(value):
+        return int(round(float(value or 0) * 100,0))
+
+    @staticmethod
+    def formatCountShow(value):
+        return '%.2f' % (float(value or 0)/100.0)
+
+    @staticmethod
+    def formatPriceShow(value):
+        return '%.2f' % (float(value or 0)/100.0)
+
+    @staticmethod
+    def formatAmount(value):
+        return int(round(float(value or 0) * 10000,0))
+
+    @staticmethod
+    def formatAmountShow(value):
+        return '%.2f' % (float(value or 0) / 10000.0 + 0.0000001)
+
+class CustomFormaterByUser():
+    @staticmethod
+    def formatEmptyStr(value,user):
+        return Formater.formatEmptyStr(value)
+
+    @staticmethod
+    def formatEmptyFloat(value, user):
+        #return round(value or 0, 2)
+        return Formater.formatEmptyFloat(value)
+
+    @staticmethod
+    def formatTime(value,user):
+        return Formater.formatTime(value)
+
+    @staticmethod
+    def formatDate(value, user):
+        return Formater.formatDate(value)
+
+    @staticmethod
+    def formatCountShow(value,user):
+        return Formater.formatCountShow(value)
+    @staticmethod
+    def formatPartAmountShow(value, user):
+        return Formater.formatAmountShow(value)
+
+class ExcelImporter():
+    def getExcelData(self,file):
+        if not file:
+            raise CustomError(u'请上传数据文件')
+        try:
+            data = tablib.import_set(file.read(), format='xlsx').dict
+            if not len(data):
+                raise CustomError(u'上传的文件内没有发现数据')
+            return data
+        except InvalidFileException:
+            raise CustomError(u'请上传<strong>xlsx</strong>格式的数据文件,老版本的xls格式不被支持!')
+
+    def validRow(self,row):
+        data = {}
+        for (k,v) in self.fields.items():
+            is_required = v[0]
+            format_proc = v[1]
+
+            try:
+                value = row[k]
+            except:
+                raise CustomError(u'缺少[%s]列,请检查模板文件或重新下载' % k)
+
+            if is_required and value == None:
+                raise CustomError(u'%s:为必填项' % k)
+
+            if format_proc and value != None:
+                try:
+                    value = format_proc(value)
+                except CustomError as e:
+                    raise CustomError(u'%s:错误的值[%s]' % (k,e.get_error_msg()))
+
+            data[k] = value
+        return data
+
+    @staticmethod
+    def formatUnicode(value):
+        try:
+            return str(value).strip(u' ')
+        except:
+            return ''
+
+    @staticmethod
+    def formatInt(value):
+        try:
+            return int(float(str(value).strip(u' ')))
+        except:
+            raise CustomError(u'不能转换为整数')
+
+    @staticmethod
+    def formatFloat(value):
+        try:
+            return round(float(str(value).strip(u' ')) or 0,2)
+        except:
+            raise CustomError(u'不能转换为小数')
+
+    @staticmethod
+    def formatFloatGtZ(value):
+        v = ExcelImporter.formatFloat(value)
+        if v <= 0:
+            raise CustomError(u'必须大于0')
+        return v
+
+    @staticmethod
+    def formatFloatGeZ(value):
+        v = ExcelImporter.formatFloat(value)
+        if v < 0:
+            raise CustomError(u'必须大于等于0')
+        return v
+
+    @staticmethod
+    def formatTel(value):
+        try:
+            value = str(value).strip(u' ')
+        except:
+            value = ''
+        if value == '':
+            return value
+        is_mobile = re.match(r'^0?(13[0-9]|14[0-9]|15[0-9]|17[0-9]|18[0-9]|19[0-9]|166)[0-9]{8}$', value)
+        is_tel = re.match(r'^([0-9]{3,4}-)?[0-9]{7,8}$', value)
+        if not (is_mobile or is_tel):
+            raise CustomError(u'不合法的手机号或者固话号')
+        return value
+
+    @staticmethod
+    def formatDate(value):
+        try:
+            return datetime.datetime.strptime(value, '%Y-%m-%d').date()
+        except:
+            raise CustomError(u'日期格式错误,格式为:2018-01-01')

+ 0 - 0
apps/dashboard/__init__.py


+ 11 - 0
apps/dashboard/views.py

@@ -0,0 +1,11 @@
+#coding=utf-8
+
+from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
+
+def index(request):
+    user_id = request.META.get('HTTP_USER_ID')
+    token = request.META.get('HTTP_ACCESS_TOKEN')
+    if not user_id or not token:
+        return HttpResponseRedirect('/views/account/login.html')
+    else:
+        return HttpResponsePermanentRedirect("/views/index.html")

+ 0 - 0
apps/log/__init__.py


+ 20 - 0
apps/log/filters.py

@@ -0,0 +1,20 @@
+# coding=utf-8
+
+import django_filters
+
+from .models import BizLog
+
+from utils.format import clean_datetime_range
+
+
+class BizLogFilter(django_filters.FilterSet):
+    type = django_filters.ChoiceFilter(choices=BizLog.TYPE_CHOICES, field_name='type')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+
+    class Meta:
+        model = BizLog
+        fields = ('create_time', 'type', )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(BizLogFilter, self).__init__(data, *args, **kwargs)

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


+ 54 - 0
apps/log/models.py

@@ -0,0 +1,54 @@
+# 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
+    IMPORT = 4
+    TYPE_CHOICES = (
+        (INSERT, u'添加'),
+        (UPDATE, u'修改'),
+        (DELETE, u'删除'),
+        (IMPORT, 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 = ()

+ 184 - 0
apps/serializer_errors.py

@@ -0,0 +1,184 @@
+#coding=utf-8
+
+from rest_framework import fields
+
+def dump_serializer_errors(serializer):
+    error_messages = {
+        u'blank': u'此字段不能为空',
+        u'required': u'此字段必填',
+        u'max_length':u'此字段不超过%d个字符',
+        u'min_length':u'此字段不少于%d个字符',
+        u'null': u'该字段不能为空'  # This field may not be null
+    }
+    field_error_messages = {
+        fields.BooleanField:{
+            u'invalid': u'此字段不是一个合法的布尔值'
+        },
+        fields.CharField: {
+            u'invalid': u'此字段不是一个合法的字符串'
+        },
+        fields.NullBooleanField: {
+            u'invalid': u'此字段不是一个合法的布尔值'
+        },
+        fields.EmailField: {
+            u'invalid': u'邮箱格式不正确'
+        },
+        fields.RegexField: {
+            u'invalid': u'此字段没有匹配的一个必要的模板'
+        },
+        fields.SlugField: {
+            u'invalid': u'此字段不是一个合法的slug,应该由字母,数字,下划线,连字符组成',
+            u'invalid_unicode': u'此字段不是一个合法的slug,应该由unicode字母,数字,下划线,连字符组成'
+        },
+        fields.URLField: {
+            u'invalid': u'此字段不是一个合法的URL'
+        },
+        fields.UUIDField: {
+            u'invalid': u'此字段不是一个合法的UUID'
+        },
+        fields.IPAddressField: {
+            u'invalid': u'此字段不是一个合法的IPv4或者IPv6地址'
+        },
+        fields.IntegerField: {
+            u'invalid': u'此字段不是一个合法的整数',
+            u'max_value': u'此字段的数值要小于等于%d',
+            u'min_value': u'此字段的数值要大于等于%d',
+            u'max_string_length': u'此字段长度超过1000',
+        },
+        fields.FloatField: {
+            u'invalid': u'此字段不是一个合法的浮点数',
+            u'max_value': u'此字段的数值要小于等于%d',
+            u'min_value': u'此字段的数值要大于等于%d',
+            u'max_string_length': u'此字段长度超过1000',
+        },
+        fields.DecimalField: {
+            u'invalid': u'此字段不是一个合法的数字',
+            u'max_value': u'此字段的数值要小于等于%d',
+            u'min_value': u'此字段的数值要大于等于%d',
+            u'max_digits': u'此字段的数值不超过%d位',
+            u'max_decimal_places': u'此字段十进制位不超过%d',
+            u'max_whole_digits': u'此字段小数点前的数字不超过%d。',
+            u'max_string_length': u'此字段长度超过1000',
+        },
+        fields.DateTimeField: {
+            u'invalid': u'日期时间格式不合法',
+            u'date': u'不能只输入日期,应输入日期和时间',
+            u'make_aware': u'时区%d不合法',
+            u'overflow': u'日期时间超出范围',
+        },
+        fields.DateField: {
+            u'invalid': u'日期格式不合法',
+            u'date': u'应输入日期,不能输入日期和时间',
+        },
+        fields.TimeField: {
+            u'invalid': u'时间格式不合法',
+        },
+        fields.DurationField: {
+            u'invalid': u'时间段格式不合法,应该用格式:%d',
+        },
+        fields.ChoiceField: {
+            u'invalid_choice': u'此字段不是一个合法的选项',
+        },
+        fields.MultipleChoiceField: {
+            u'invalid_choice': u'此字段不是一个合法的选项',
+            u'not_a_list': u'应输入数组(list)格式',
+            u'empty': u'此选项不能为空',
+        },
+        fields.FilePathField: {
+            u'invalid_choice': u'此字段不是一个合法的路径',
+        },
+        fields.FileField: {
+            u'required': u'没有提交文件',
+            u'invalid': u'提交的不是文件,请检查表单的编码类型',
+            u'no_name': u'没有文件名',
+            u'empty': u'提交的是一个空文件',
+            u'max_length': u'文件内容要小于%d个字符',
+        },
+        fields.ImageField: {
+            u'invalid_image': u'提交的不是图片文件或者图片文件损坏,请提交一个合法的图片文件',
+        },
+        fields.ListField: {
+            u'not_a_list': u'此字段应传入数组(list)类型',
+            u'empty': u'传入的list不能为空',
+            u'min_length': u'该字段至少要%d个元素',
+            u'max_length': u'该字段最多能有%d个元素',
+        },
+        fields.DictField: {
+            u'not_a_dict': u'此字段应传入字典(dict)类型',
+        },
+        fields.JSONField: {
+            u'invalid': u'此字段不是一个合法的JSON类型'
+        },
+        fields.ModelField: {
+            u'max_length': u'此字段长度要小于等于%d个字符'
+        }
+    }
+
+    result = [u'数据错误<br />', ]
+    for i in range(0, len(serializer.errors.keys())):
+        k = serializer.errors.keys()[i]
+        v = serializer.errors.values()[i]
+        field = serializer.fields.get(k)
+
+        label = field.label
+        if label:
+            label += ' - '
+        else:
+            label = ''
+
+        error_detail = v[0]
+        try:
+            msg = field_error_messages[type(field)][error_detail.code]
+            if type(field) == fields.IntegerField or type(field) == fields.FloatField:
+                if error_detail.code == u'max_value':
+                    msg = msg % field.max_value
+                if error_detail.code == u'min_value':
+                    msg = msg % field.min_value
+
+            if type(field) == fields.DecimalField:
+                if error_detail.code == u'max_value':
+                    msg = msg % field.max_value
+                if error_detail.code == u'min_value':
+                    msg = msg % field.min_value
+                if error_detail.code == u'max_digits':
+                    msg = msg % field.max_digits
+                if error_detail.code == u'max_decimal_places':
+                    msg = msg % field.decimal_places
+                if error_detail.code == u'max_whole_digits':
+                    msg = msg % field.max_whole_digits
+
+            if type(field) == fields.DateTimeField:
+                if error_detail.code == u'make_aware':
+                    msg = msg % field.timezone
+
+            if type(field) == fields.DurationField:
+                if error_detail.code == u'invalid':
+                    msg = msg % u'[DD] [HH:[MM:]]ss[.uuuuuu]'
+
+            if type(field) == fields.DateTimeField:
+                if error_detail.code == u'make_aware':
+                    msg = msg % field.timezone
+
+            if type(field) == fields.ListField:
+                if error_detail.code == u'min_length':
+                    msg = msg % field.min_length
+                if error_detail.code == u'max_length':
+                    msg = msg % field.max_length
+
+            if type(field) == fields.ModelField:
+                if error_detail.code == u'max_length':
+                    msg = msg % field.max_length
+
+        except:
+            try:
+                msg = error_messages[error_detail.code]
+                if error_detail.code == u'max_length':
+                    msg = msg % field.max_length
+                if error_detail.code == u'min_length':
+                    msg = msg % field.min_length
+
+            except:
+                msg = str(error_detail)
+        msg = label + msg
+        result.append(msg)
+    return u'<br />'.join(result)

+ 0 - 0
apps/upload/__init__.py


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


+ 131 - 0
apps/upload/models.py

@@ -0,0 +1,131 @@
+# coding=utf-8
+import os
+
+from PIL import Image
+from django.conf import settings
+from django.db import models
+from utils.file_operation import UploadFile, DeleteFile
+from django.utils import timezone
+from apps.agent.models import Store
+
+class UploadManager(models.Manager):
+
+    # def employee_addnew(self, employee, type, file):
+    #     return self._addnew(employee.tenant, employee.user, type, file)
+    #
+    # def customer_addnew(self, customer, type, file):
+    #     return self._addnew(customer.tenant, customer.user, type, file)
+
+    def _addnew(self, tenant, user, type, file):
+        width = None
+        height = None
+
+        path = UploadManager.calculatePath(type, tenant.id)
+        filename = UploadFile(file, path, user.id)
+        fullname = "%s%s" % (settings.MEDIA_ROOT, filename)
+        size = os.path.getsize(fullname)
+
+        try:
+            img = Image.open(fullname)
+            width, height = img.size
+            # 缩略图压缩宽或高最大200
+            if width > 1440:
+                img = img.resize((1440, int((height / width) * 1440)), Image.ANTIALIAS)
+                img.save(fullname)
+                width, height = img.size
+        except:
+            pass
+
+        instance = self.model(
+            tenant=tenant,
+            user=user,
+            type=type,
+            name=file.name,
+            picture="%s%s" % (settings.MEDIA_URL, filename),
+            width=width,
+            height=height,
+            file_size="%.2f" % (float(size)/1024),
+        )
+        instance.save()
+        return instance
+
+    def _addnew_voice(self, tenant, user, type, file, time):
+
+        path = UploadManager.calculatePath(type, tenant.id)
+        filename = UploadFile(file, path, user.id)
+        fullname = "%s%s" % (settings.MEDIA_ROOT, filename)
+        size = os.path.getsize(fullname)
+
+        instance = self.model(
+            tenant=tenant,
+            user=user,
+            type=type,
+            name=file.name,
+            picture="%s%s" % (settings.MEDIA_URL, filename),
+            width=200,
+            height=200,
+            file_size="%.2f" % (float(size)/1024),
+            voice_time=time,
+        )
+        instance.save()
+        return instance
+
+    @staticmethod
+    def calculatePath(type, tenant_id):
+        path_map = {
+            Upload.REPAIR_IMAGE: repair_image,
+            Upload.INSPECTION_IMAGE: inspection_image,
+            Upload.COMPANY_IMAGE: company_image,
+            Upload.REPAIR_VOICE:repair_image,
+        }
+
+        return path_map[type] + str(tenant_id)  + '/'
+
+
+repair_image = "repair/"
+inspection_image = "inspection/"
+company_image = "company/"
+
+class Upload(models.Model):
+    REPAIR_IMAGE = 1
+    INSPECTION_IMAGE = 2
+    COMPANY_IMAGE = 3
+    REPAIR_VOICE = 4
+    TYPE_CHOICES = (
+        (REPAIR_IMAGE, u'报修图片'),
+        (INSPECTION_IMAGE, u'巡检图片'),
+        (COMPANY_IMAGE, u'营业执照图片'),
+        (REPAIR_VOICE, u'报修语音')
+    )
+
+    store = models.ForeignKey(Store, verbose_name=u'门店', on_delete=models.PROTECT)
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'操作人', on_delete=models.PROTECT)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类型")
+    name = models.CharField(verbose_name=u'图片名', max_length=250)
+    picture = models.CharField(verbose_name=u'图片路径', max_length=250)
+    width = models.IntegerField(verbose_name=u"图片宽度", blank=True, default=0)
+    height = models.IntegerField(verbose_name=u"图片高度", blank=True, default=0)
+    create_time = models.DateTimeField(verbose_name=u'上传时间', auto_now_add=True, editable=False)
+    file_size = models.FloatField(verbose_name="文件大小",blank=True, default=0)
+
+    objects = UploadManager()
+
+    class Meta:
+        db_table = 'system_upload'
+        verbose_name = u'文件上传'
+        ordering = ['-create_time']
+        index_together = (
+            'type',
+            'create_time',
+        )
+        default_permissions = ()
+
+    def del_images(self):
+        picture = self.picture
+        self.delete()
+        DeleteFile(picture)
+
+    def get_path(self):
+        return '%s%s' % (settings.SERVER_DOMAIN, self.picture)
+
+

+ 12 - 0
apps/upload/serializers.py

@@ -0,0 +1,12 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from django.conf import settings
+from .models import Upload
+
+
+class UploadSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Upload
+        fields = ('picture', 'width', 'height', 'type', 'voice_time')

+ 0 - 0
decorate/__init__.py


+ 254 - 0
decorate/settings.py

@@ -0,0 +1,254 @@
+# coding=utf-8
+"""
+Django settings for ly_baoxiu_admin 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.log',
+    'apps.WechatApplet',
+    'apps.WechatTp',
+    'apps.api',
+    'apps.upload',
+    'apps.agent',
+
+]
+
+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 = 'decorate.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 = 'decorate.wsgi.application'
+
+# Database
+# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.mysql',
+        'NAME': 'decorate',
+        'USER': 'carwin',
+        'PASSWORD': 'carwin!@#',
+        'HOST': '192.168.2.45',
+        'PORT': 3306,
+    },
+    # 'default': {
+    #     'ENGINE': 'django.db.backends.mysql',
+    #     'NAME': 'baoxiu',
+    #     'USER': 'baoxiu',
+    #     'PASSWORD': 'baoxiu!@#',
+    #     'HOST': '106.12.149.71',
+    #     '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',
+    },
+]
+
+FEMALE = 1
+MALE = 2
+GENDER_CHOICES = (
+    (FEMALE, u'女'),
+    (MALE, u'男'),
+)
+
+DEFAULT = 0
+PASS = 1
+CHECK_STATE_CHOICES = (
+    (DEFAULT, u'待审核'),
+    (PASS, 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/'
+STATIC_ROOT = os.path.join(BASE_DIR, "uis/static/")
+STATICFILES_DIRS = (
+    os.path.join(BASE_DIR, "static"),
+)
+
+MEDIA_URL = '/up/'
+MEDIA_ROOT = os.path.join(BASE_DIR, "uis/up/")
+
+UIS_URL = '/'
+UIS_ROOT = os.path.join(BASE_DIR, "uis/")
+
+# SERVER_DOMAIN = 'https://baoxiu.zzliaoyuan.com'
+SERVER_DOMAIN = 'http://192.168.2.45:8887'
+
+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名称
+}
+
+#支付宝支付
+ALIPAY_SETTING = {
+    'notify_url':'https://baoxiu.zzliaoyuan.com/api/alipay_notify/',
+    'app_id': '2016101202110194',
+    'app_private_key': 'MIIEpAIBAAKCAQEArkjhoebB4tmy7oXxQZ8RyUH3CSqJnJSFsfZ/Ntjga+R7UGZ5NEBm92UeK2O6dYM/iSWBzIDjL2i5uqk3GdjJ+EUv1fN1oz0FjygDvaNDjo7FkgN+tRI4iSFkL/kRE+Rf0NQo1mZntqVm26ySMmsk0qr8JH4HE/3R6FiDiBvMezvYEWP/4QpmFbxQjeY3JXixJX+9Yf3mZMOW8OPNwA9zkD/5tSNvRQTdgl4aMVB6eGcJ2zhx+usJjdkITAuppzHKMdRgMpaLTTcyN8e62VRM8FUgmvhb6cyc1lob+cOswHR3Bkjw0CbM+WRbWBIq3kdhpkOB1QVanaWpFBTHVL3CNwIDAQABAoIBAGynr1OaY6IkCvO9ua0pmJMadwLV7DkcN0W94Y22JCPfrClx2XqD9C2kcuymHicJDmFYatWuPPTfm3sN5/ZLO492wt3mDJ8aeg7H6Z0e3g1QYUgouaP1mh2UHbvy0XzPUhUFT/hqm5o5AClQK00XkztrsgqmL499HJJ4LHh8rDzfCHAejxzZvczJJjW1Rozxot471FGqH61fRnkY1p2sTjfBw3TJpEtXUg/EAptDpa0Tu9HzC2LiyH7gM/kc7X35j2wbtyTIQ6ut0T0SW/BRsqPY5w3nFgMft/sDIMWWAbcMb1mX9JQvhxJajU+ZoYlRzUVPmheiqvYfY3NdATlBhuECgYEA4NN5+/9uM8H1y3GcARu4jt0YhhEHM9jxINxvN71WBzvGGikqD4sYY1ROfiz5TTj9dP4jaZtKwsbOpbCm43JMDOktEkbB6Gc6zWSvtvDsrVpzdGylF8vx1ziT0U1CFpXdkiXM3u4u8pknrJvSpdi+nGL9Lw/ajRR0BjBcbz1hdx8CgYEAxnNdsg5kLZMaLAu9SJU4v3mfE6Shujsgsc0h0bTDDD2E3jcLEuD1+ssdN0uxDqRXqzVvLIBeep8JXWX3rHyoM9GMwWSlzesaExP1NbPdHCYEsRAmNFNzxWLbwk4HT7wAJ1ecUvgI80N+KQOOrBAaKpuzsw1eA6DQIjbjCMRfyekCgYBbdSvWW15VJ735eMnhmYlGdKKZRywK52GP4JxNrHPmlWZNFhnKIOdW4ODdayYvR9OzV/7H4yhSe9VsiaQaOlC9n8159dbD19qP2zmvzeo5yuXUoq22NHy49IcNScRss6Ji5YWl2na2sZ72FJ1oYfeIXqxoDKb3ZS6dpea0Sd/wOQKBgQC71MnkE/cBo+/rrJjVsFdr8+N7JiHlML27TFssL5lFZpzx7stObRqk68aVZdEJ+Mb5x7OYPVAvY4ProO2RltNlaHt/OJIums2tLQC0ea9YSyu5o31lnW9AblduS4hkEZ9bnXLanOVryoAItdM+TGdX2NN3L7nCWV1mZOjyjVvUsQKBgQDLRNdf/BLhVYUOJe/cJ66JXMS2gBkMf0EnUdCMkZ+cidAonl4Dtrv8+b8haeuqL55SuPWUtvVD2VmLVOaZIE0yxGeVEW2jVi0FwiZoNFjntuJHuwoerFSW4AobajIy4xeg3LWSqxD2nKWxTCAXumwGozCdnCgCWQSp3+L+8Xu6oA==',
+    'alipay_public_key': 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlfOIgQCOmHu1qMdmrOlS3+mMmNab9ZWTmm6R0QP91+E9ShNQbBRizE/muY9QYowp4smCeZBxuVCq5KRDJqcCq/o0DKc2tW/wpBFZaWzIngJsxuwTdRX6zsmM1u/c6sgcPY4rNYc6Gs2Ago8imKceHIcCQqhXb8+m3JOlgTd/EnRFlufPyu8WwUWQqRGfLM95g03bC7yH/Bh7GU7yrx0pvBp9ywW13SyiM9Kgw8h291AzZLJHFGD0mkvKKAiU951WprbjmFG5gE90vhHdQJtUEBm1m5Hn7SWR2iMlUbS2y8GioFkOUOra3wt+BJZ8yw/Z3XeJj9LacYgCO1B/EIeSlwIDAQAB',
+}
+# 微信登录、支付
+WECHAT = {
+    'appid': 'wxab1e10bcf7650601',  # 微信 服务号
+    'mchid': '1339217801',                        # 商户号
+    'notify_url': 'https://baoxiu.zzliaoyuan.com/api/wechat_notify/',          # 通知地址
+    'merchant_key': '4fmqm5axuBcI1jYK3uGvfaOLdk8coB6A',                  # 商户平台API密钥
+    'spbill_create_ip':'123.56.60.154',
+}
+PAY_SUBJECT = u'装集客'
+# 导入本地设置
+try:
+    from ly_baoxiu_admin.local_settings import *
+except ImportError:
+    pass

+ 32 - 0
decorate/urls.py

@@ -0,0 +1,32 @@
+# 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
+
+urlpatterns = [
+    url(r'^$', index),
+    url(r'^account/', include('apps.account.urls')),
+    url(r'^api/', include('apps.api.urls')),
+]
+
+urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+urlpatterns += static(settings.UIS_URL, document_root=settings.UIS_ROOT)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

+ 16 - 0
decorate/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for haly_baoxiu_admingrid 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', 'ly_baoxiu_admin.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', 'decorate.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()

+ 13 - 0
requirements

@@ -0,0 +1,13 @@
+django==2.2.5
+django-filter
+djangorestframework
+djangorestframework-jwt
+django-cors-headers
+mysqlclient
+Pillow
+pycryptodome
+requests
+xmltodict
+tablib
+openpyxl
+alipay-sdk-python

BIN
static/imgs/aliPay.png


BIN
static/imgs/wechart.png


BIN
static/xls/设备信息导入模板.xlsx


+ 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


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


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


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 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


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 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


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно