CRM(Customer Relationship Manager)客戶關係管理系統javascript
企業爲提升核心競爭力,利用相應的信息技術以及互聯網技術協調企業與顧客間在銷售、營銷和服務上的交互,從而提高其管理方式,向客戶提供創新式的個性化的客戶交互和服務的過程。其最終目標是吸引新客戶、保留老客戶以及將已有客戶轉爲忠實客戶,增長市場。css
業務場景分析意在各個用戶使用 CRM 用來作什麼,可以更好地別寫項目。在這裏咱們編寫一個基於培訓機構的 CRM。html
成員及使用場景前端
成員 | 說明 | 成員 | 說明 |
---|---|---|---|
機構銷售 | 尋找學員報名、給學員報名,分配班級等 | 學員 | 諮詢課程,繳納學費 |
講師 | 查看班級列表、建立課程記錄、佈置做業,批改做業等 | 管理員 | 建立課程、校區、班級,分配權限等 |
不一樣的用戶有訪問 crm,看到的菜單也是不同的,這也是最基本的一種權限體現,好比:java
經過用戶、菜單,以及角色模型便可實現:python
菜單分爲固定(absolute)和動態菜單(dynamic)兩種,動態菜單人人均可以看到(如:首頁),而固定菜單不一樣的角色看到的都不同:jquery
另外動態菜單的 url_anem
要與路由中 name
一致:git
crm/views.py
github
from django.urls import path, re_path from crm import views urlpatterns = [ path('dashboard/', views.dashboard, name='sales_dashboard'), # 首頁 url_name 與這個 name 一致 path('student_enrollment/', views.student_enrollment, name='student_enrollment'), ... ]
整個 CRM 整體佈局,咱們以 bootstrap 中的模板爲基礎,在此之上進行修改拓展,採用的樣式爲:django
模板地址:Dashboard
爲了減小代碼重複,在這裏採用模板繼承:
一、base.html
base.html
只是構建了一個總體框架:
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> <meta name="description" content=""> <meta name="author" content=""> <title>CRM</title> <!-- Bootstrap core CSS --> <link href="{% static 'css/bootstrap.css' %}" rel="stylesheet"> <!-- dashboard 這個模板的 css 樣式 --> <link href="{% static 'css/dashboard.css' %}" rel="stylesheet"> {% block extra-css %} {% endblock %} </head> <body> <!--主要內容區--> {% block body %} {% endblock %} <!-- JS 文件 ================================================== --> <!-- 放置在文檔的末尾,以便頁面加載更快 --> <script src="{% static 'js/jquery-3.3.1.js' %}"></script> <script src="{% static 'js/bootstrap.js' %}"></script> </body> </html>
二、index.html
index.html
繼承 base.html
,這裏主要有 "頂部導航欄、左側菜單以及右側主要內容區"
{% extends 'include/base.html' %} {% block body %} <!--頂部導航欄--> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">{% block title %}Project name{% endblock %}</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li class="dropdown "> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">{{ request.user.name }} <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">我的信息</a></li> <li><a href="{% url 'logout' %}">登出</a></li> </ul> </li> </ul> </div> </div> </nav> <!--左側菜單和右側內容區--> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> {% for role in request.user.role.all %} {% for menu in role.menus.all %} {% if request.path == menu.url_name %} <li class="active"><a href="{% if menu.url_type == 0 %} {{ menu.url_name }}{% else %}{% url menu.url_name %}{% endif %}">{{ menu.name }}</a> </li> {% else %} <li> <a href="{% if menu.url_type == 0 %} {{ menu.url_name }}{% else %}{% url menu.url_name %}{% endif %}">{{ menu.name }}</a> </li> {% endif %} {% endfor %} {% endfor %} </ul> </div> <!--右側內容區--> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> {% block right-content-container %} <h1 class="page-header">Dashboard</h1> {% endblock %} </div> </div> </div> {% endblock %}
三、CRM 頁面
CRM 頁面只用繼承 index.html
便可,dashboard.html
:
{% extends 'include/index.html' %} {% block right-content-container %} <h1 class="page-header">首頁</h1> {% endblock %}
整個 CRM 的報名流程大體分爲如下幾步:
crm/urls.py
from django.urls import path, re_path from crm import views urlpatterns = [ path('dashboard/', views.dashboard, name='sales_dashboard'), path('student_enrollment/', views.student_enrollment, name='student_enrollment'), # 發起報名 re_path(r'^enrollment/(\d+)/$', views.enrollment, name='enrollment'), # 報名連接 re_path(r'^enrollment/(\d+)/fielupload/$', views.enrollment_fileupload, # 證件上傳name='enrollment_fileupload'), re_path(r'^student_enrollment/(\d+)/contract_audit/$', views.contract_audit, # 合同審覈name='contract_audit'), ]
建立報名連接
銷售訪問:<http://127.0.0.1:8002/crm/student_enrollment/>
發起報名,會在後臺建立一條報名記錄,並返回報名連接,銷售將連接發給學員填寫:
@login_required def student_enrollment(request): """銷售分配學員班級,並生成報名連接""" customer_data = models.CustomerInfo.objects.all() class_list_data = models.ClassList.objects.all() if request.method == "POST": customer_id = request.POST.get('customer_id') # 客戶 class_grade_id = request.POST.get('class_grade_id') # 班級 consultant_id = request.user.id # 課程顧問 try: # 建立報名記錄 enrollment_obj = models.StudentEnrollment.objects.create( customer_id=customer_id, class_grade_id=class_grade_id, consultant_id=consultant_id ) except IntegrityError as e: enrollment_obj = models.StudentEnrollment.objects.get(customer_id=customer_id, class_grade_id=class_grade_id) # 學員是否贊成協議,是則跳轉到合同審覈頁面,不然 # 這裏意思是,若是學員填寫了報名表,那麼 StudentEnrollment 中 contract_agreed 是有記錄的,所以當學員填寫了報名表,銷售再次點擊報名頁面中下一步時,會跳轉到合同審覈頁面 if enrollment_obj.contract_agreed: return redirect('/crm/student_enrollment/%s/contract_audit/' % enrollment_obj.id) # 生成報名連接,傳遞給前端,銷售複製發送給學員填寫報名信息 enrollment_links = 'http://localhost:8002/crm/enrollment/%s/' % enrollment_obj.id return render(request, 'crm/student_enrollment.html', locals())
報名表
檢查用戶是否勾選合同協議及是否上傳證件(經過 onsubmit
事件,form 表單提交前執行哪一個函數):
<form method="post" onsubmit="return BeforeRemoveDisabled()" novalidate> <!-- 合同協議 --> <pre style="height: 400px">{{ enrollment_obj.class_grade.contract_template.content }}</pre> <input type="checkbox" name="contract_agreed"> 我已認真閱讀完畢,無條件贊成! <input type="submit" value="提交" class="btn btn-info pull-right"> </form>
<script> // 表單提交前,移除全部的 disabled,form 表單不能提交 disabled function BeforeRemoveDisabled() { $(':disabled').removeAttr('disabled'); // 若是沒有上傳證件信息,提示上傳 if ($('#uploaded_files').children().length == 0){ alert('請上傳證件信息!'); return false } // 若是沒有勾選協議,表單不能提交 if (!$("input[name='contract_agreed']").prop('checked')) { alert('請勾選合同協議!'); return false } } </script>
使用 drop-zone 插件實現證件上傳
<!-- 證件上傳 --> <div class="file-upload"> <h3>身份證上傳</h3> <ul id="uploaded_files"> </ul> <form id="myAwesomeDropzone" action="{% url 'enrollment_fileupload' enrollment_obj.id %}" class="dropzone"> <div class="fallback"> <input name="file" type="file" multiple/> </div> </form> </div>
<script src="{% static 'plugins/dropzone/dropzone.js' %}"></script> <script> // "myAwesomeDropzone" is the camelized version of the HTML element's ID Dropzone.options.myAwesomeDropzone = { paramName: "file", // 用於傳輸文件的名稱 maxFilesize: 2, // MB 最大不能上傳超過 2 M maxFiles: 2, // 最多上傳 2 個文件 parallelUploads: 1, // 單次上傳 1 個 accept: function (file, done) { if (file.name == "justinbieber.jpg") { done("Naha, you don't."); } else { done(); } } }; // 避免重複建立 Dropzone Dropzone.options.myAwesomeDropzone = false; // 上傳成功回調,返回值存在 response 中 $(function () { // Now that the DOM is fully loaded, create the dropzone, and setup the // event listeners // Prevent Dropzone from auto discovering this element: {# Dropzone.options.myAwesomeDropzone = false;#} var myDropzone = new Dropzone("#myAwesomeDropzone"); myDropzone.on("success", function (file, response) { /* Maybe display some more file information on your page */ console.log("success", file, file.name, response); var response = JSON.parse(response); if (!response.status) { alert(response.error); } else { var ele = "<li class='file_title'>" + file.name + ' ' + "<span class='glyphicon glyphicon-remove'></span>" + "</li>" ; $("#uploaded_files").append(ele); alert(response.message) } }); }); // $('.file_title').children('span') /* $('#uploaded_files').on('click', 'span', function () { alert(123); }) */ $('#uploaded_files').on('click', 'span', function () { $(this).parent().remove(); })
提交報名表後:
合同審覈
銷售審覈合同後,將跳轉到數據修改頁面:
以上就是 CRM 的大體開發流程,具體源碼可參考:<https://github.com/hj1933/PerfectCRM>