CRM客户关系管理系统(二)
第三章、前端页面设计
3.1.前端页面布局
(1)静态文件
新建statics目录(存放css/fonts/imgs/js/plugins)
settings配置
STATIC_URL = \'/static/\' STATICFILES_DIRS = ( os.path.join(BASE_DIR, \'statics\'), )
(2)模板文件
templates下新建crm目录,把Dashboard Template for Bootstrap.html放到里面,命名为dashboard.html
{#templates/crm/dashboard.html#}
{% extends \'index.html\' %}
templates下新建base.html(主要存放css和js)
{#templates/base.html#} {% load staticfiles %} <!DOCTYPE html> <!-- saved from url=(0042)https://v3.bootcss.com/examples/dashboard/ --> <html lang="zh-CN"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content=""> <meta name="author" content=""> {# <link rel="icon" href="https://v3.bootcss.com/favicon.ico">#} <title>PerfectCRM</title> <!-- Bootstrap core CSS --> <link href="{% static \'css/bootstrap.min.css\' %}" rel="stylesheet"> <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> <link href="{% static \'css/ie10-viewport-bug-workaround.css\' %}" rel="stylesheet"> <!-- Custom styles for this template --> <link href="{% static \'css/dashboard.css\' %}" rel="stylesheet"> <script src="{% static \'js/ie-emulation-modes-warning.js\' %}"></script> </head> <body> {% block body %} {% endblock %} <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="{% static \'js/jquery.min.js\' %}"></script> <script src="{% static \'js/bootstrap.min.js\' %}"></script> <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> <script src="{% static \'js/ie10-viewport-bug-workaround.js\' %}"></script> </body></html>
templates下新建index.html(body里面的代码)
此时目录
(3)配置url
PerfectCRM/urls.py
# PerfectCRM/urls.py from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ url(r\'^admin/\', admin.site.urls), url(r\'^crm/\', include(\'crm.urls\')), ]
crm/urls.py
# crm/urls.py from django.conf.urls import url,include from crm import views urlpatterns = [ url(r\'^$\', views.dashboard), ]
现在访问http://127.0.0.1:8000/crm/,就可以显示正常页面了
(4)index.html修改
- 删除search+右上角留一个就好
- 左侧project改成block
- Dashboard改成h2,删除class “row placeholders”里面的内容
- 删除class “sub-header“”里面的内容
- 左边ul只留一个就好
{#templates/index.html#}
{% extends \'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 pro_name %}Project name{% endblock %}</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="https://v3.bootcss.com/examples/dashboard/#">Dashboard</a></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">
<li class="active"><a href="https://v3.bootcss.com/examples/dashboard/#">Overview <span class="sr-only">(current)</span></a></li>
<li><a href="https://v3.bootcss.com/examples/dashboard/#">Reports</a></li>
<li><a href="https://v3.bootcss.com/examples/dashboard/#">Analytics</a></li>
<li><a href="https://v3.bootcss.com/examples/dashboard/#">Export</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h2 class="page-header">Dashboard</h2>
</div>
</div>
</div>
{% endblock %}
(5)动态菜单生成
销售,学生,讲师访问页面时。显示的应该是对应角色的菜单,所以需要动态生成菜单
crm/models.py
添加Menus
class Menus(models.Model): \'\'\'动态菜单\'\'\' name = models.CharField(max_length=64) #绝对url和动态url url_type_choices = ((0,\'absolute\'),(1,\'dynamic\')) url_type = models.SmallIntegerField(choices=url_type_choices,default=0) url_name = models.CharField(max_length=128) def __str__(self): return self.name class Meta: unique_together = (\'name\',\'url_name\')
在Role中关联Menus
class Role(models.Model): \'\'\'角色表\'\'\' name = models.CharField(max_length=64,unique=True) #不能重 #一个角色可以访问多个菜单,一个菜单可以被多个角色访问 menus = models.ManyToManyField(\'Menus\',blank=True,verbose_name=\'动态菜单\') def __str__(self): return self.name
# crm/model.py __author__ = \'derek\' from django.db import models from django.contrib.auth.models import User class Role(models.Model): \'\'\'角色表\'\'\' name = models.CharField(max_length=64,unique=True) #不能重 #一个角色可以访问多个菜单,一个菜单可以被多个角色访问 menus = models.ManyToManyField(\'Menus\',blank=True,verbose_name=\'动态菜单\') def __str__(self): return self.name class UserProfile(models.Model): \'\'\'用户信息表\'\'\' #关联django自带的User,可以自己扩展字段 user = models.ForeignKey(User,on_delete=models.CASCADE) name = models.CharField(\'姓名\',max_length=64) #一个用户可以有多个角色,一个角色可以对应多个用户 role = models.ManyToManyField(Role,blank=True,null=True) def __str__(self): return self.name class CustomerInfo(models.Model): \'\'\'客户信息表\'\'\' name = models.CharField(\'姓名\',max_length=64,default=None) contact_type_choices = ((0,\'qq\'),(1,\'微信\'),(2,\'手机\')) contact_type = models.SmallIntegerField(choices=contact_type_choices,default=0) contact = models.CharField(\'联系方式\',max_length=64,unique=True) source_choices = ((0,\'qq群\'),(1,\'51CTO\'),(2,\'百度推广\'),(3,\'知乎\'),(4,\'转介绍\'),(5,\'其它\'),) source = models.SmallIntegerField(\'客户来源\',choices=source_choices) #关联自己,如果是转介绍(介绍人已经是学员,然后介绍别人过来学习),需要填写转介绍人的信息,不是转介绍,这里就可以为空 referral_from = models.ForeignKey(\'self\',blank=True,null=True,verbose_name=\'转介绍\',on_delete=models.CASCADE) #可以咨询多个课程 consult_courses = models.ManyToManyField(\'Course\',verbose_name=\'咨询课程\') consult_content = models.TextField(\'咨询内容\',) status_choices = ((0,\'未报名\'),(1,\'已报名\'),(2,\'已经退学\')) status = models.SmallIntegerField(\'客户状态\',choices=status_choices) consultant = models.ForeignKey(\'UserProfile\',verbose_name=\'课程顾问\',on_delete=models.CASCADE) date = models.DateField(\'创建的时间\',auto_now_add=True) class Student(models.Model): \'\'\'学员表\'\'\' customer = models.ForeignKey(\'CustomerInfo\',verbose_name=\'客户\',on_delete=models.CASCADE) class_grades = models.ForeignKey(\'ClassList\',verbose_name=\'班级\',on_delete=models.CASCADE) def __str__(self): return self.customer class CustomerFollowUp(models.Model): \'\'\'客户跟踪记录表\'\'\' customer = models.ForeignKey(\'CustomerInfo\',on_delete=models.CASCADE) content = models.TextField(\'跟踪内容\',) user = models.ForeignKey(\'UserProfile\',verbose_name=\'跟进人\',on_delete=models.CASCADE) status_choices = ((0,\'近期无报名计划\'),(1,\'一个月内报名\'),(2,\'半个月报名\'),(3,\'已报名\'),) status = models.SmallIntegerField(\'客户状态\',choices=status_choices) date = models.DateField(\'创建的时间\', auto_now_add=True) class Course(models.Model): \'\'\'课程表\'\'\' name = models.CharField(\'课程名称\',max_length=64,unique=True) #价格必须为整数 price = models.PositiveSmallIntegerField(\'价格\',) period = models.PositiveSmallIntegerField(\'课程周期(月)\',default=5) outline = models.TextField(\'大纲\',) def __str__(self): return self.name class ClassList(models.Model): \'\'\'班级列表\'\'\' branch = models.ForeignKey(\'Branch\',verbose_name=\'校区\',on_delete=models.CASCADE) #一个班级只能有一个课程,一个课程可以有多个班级 course = models.ForeignKey(\'Course\',verbose_name=\'课程\',on_delete=models.CASCADE) class_type_choices = ((0,\'脱产\'),(1,\'周末\'),(2,\'网络班\')) class_type = models.SmallIntegerField(\'班级类型\',choices=class_type_choices,default=0) semester = models.SmallIntegerField(\'学期\',) teachers = models.ManyToManyField(\'UserProfile\',verbose_name=\'讲师\') start_date = models.DateField(\'开班日期\',) #毕业日期因为不固定,所以可以为空 graduate_date = models.DateField(\'毕业日期\',blank=True,null=True) def __str__(self): #班级名是课程名+第几期拼接起来的 return "%s(%s)期"%(self.course.name,self.semester) class Meta: #联合唯一,班级不能重复 unique_together = (\'branch\',\'class_type\',\'course\',\'semester\') class CourseRecord(models.Model): \'\'\'上课记录\'\'\' class_grade = models.ForeignKey(\'ClassList\',verbose_name=\'上课班级\',on_delete=models.CASCADE) day_num = models.PositiveSmallIntegerField(\'课程节次\',) teacher = models.ForeignKey(\'UserProfile\',verbose_name=\'讲师\',on_delete=models.CASCADE) title = models.CharField(\'本节主题\',max_length=64) content = models.TextField(\'本节内容\',) has_homework = models.BooleanField(\'本节有作业\',default=True) homework = models.TextField(\'作业需求\',blank=True,null=True) date = models.DateField(\'创建的时间\', auto_now_add=True) def __str__(self): #上课班级+课程节次 return "%s第(%s)节"%(self.class_grade,self.day_num) class Meta: unique_together = (\'class_grade\',\'day_num\') class StudyRecord(models.Model): \'\'\'学习记录表\'\'\' #一节课对应多个学生 course_record = models.ForeignKey(\'CourseRecord\',verbose_name=\'课程\',on_delete=models.CASCADE) #一个学生有多个上课记录 student = models.ForeignKey(\'Student\',verbose_name=\'学生\',on_delete=models.CASCADE) score_choices = ((100,\'A+\'), (90,\'A\'), (85,\'B+\'), (80,\'B\'), (75,\'B-\'), (70,\'C+\'), (60,\'C\'), (40,\'C-\'), (-50,\'D\'), (0,\'N/A\'), #not avaliable (-100,\'COPY\'), #抄作业 ) score = models.SmallIntegerField(\'得分\',choices=score_choices,default= 0) show_choices = ((0,\'缺勤\'), (1,\'已签到\'), (2,\'迟到\'), (3,\'早退\'), ) show_status = models.SmallIntegerField(\'出勤\',choices=show_choices,default=1) note = models.TextField(\'成绩备注\',blank=True,null=True) date = models.DateField(\'创建的时间\', auto_now_add=True) def __str__(self): return "%s %s %s"%(self.course_record,self.student,self.score) class Branch(models.Model): \'\'\'校区分支\'\'\' name = models.CharField(\'校区名\',max_length=64,unique=True) addr = models.CharField(\'地址\',max_length=128,blank=True,null=True) def __str__(self): return self.name class Menus(models.Model): \'\'\'动态菜单\'\'\' name = models.CharField(max_length=64) #绝对url和动态url url_type_choices = ((0,\'absolute\'),(1,\'dynamic\')) url_type = models.SmallIntegerField(choices=url_type_choices,default=0) url_name = models.CharField(max_length=128) def __str__(self): return self.name class Meta: unique_together = (\'name\',\'url_name\')
生成表,然后注册到后台
admin.site.register(models.Menus)
# crm/admin.py from django.contrib import admin from crm import models admin.site.register(models.Role) admin.site.register(models.CustomerInfo) admin.site.register(models.Student) admin.site.register(models.CustomerFollowUp) admin.site.register(models.Course) admin.site.register(models.ClassList) admin.site.register(models.CourseRecord) admin.site.register(models.StudyRecord) admin.site.register(models.Branch) admin.site.register(models.Menus) admin.site.register(models.UserProfile)
开始创建菜单,角色,用户之前
首先修改UserProfile的user字段为OneToOneField
然后还要有登录界面
3.2.登录页面开发
(1)templates/login.html
{#templates/login.html#} {% extends \'index.html\' %} {% load staticfiles %} {% block extra-css %} <link rel="stylesheet" href="{% static \'css/signIn.css\' %}"> {% endblock %} {% block body %} <div class="container"> <form class="form-signin" method="post"> {% csrf_token %} <h2 class="form-signin-heading">仙剑奇侠传</h2> <label for="inputEmail" class="sr-only">Username</label> <input type="text" id="" name="username" class="form-control" placeholder="Username" required autofocus> <label for="inputPassword" class="sr-only">Password</label> <input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required> <div class="checkbox"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> </form> </div> <!-- /container --> {% endblock %}
(2)statics/css/signin.css
body { padding-top: 40px; padding-bottom: 40px; background-color: #eee; } .form-signin { max-width: 330px; padding: 15px; margin: 0 auto; } .form-signin .form-signin-heading, .form-signin .checkbox { margin-bottom: 10px; } .form-signin .checkbox { font-weight: normal; } .form-signin .form-control { position: relative; height: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; }
(3)PerfectCRM/urls.py
url(r\'^login/\', views.acc_login),
(4)PerfectCRM/views.py
# PerfectCRM/views.py from django.shortcuts import render def acc_login(request): return render(request,\'login.html\')
访问:http://127.0.0.1:8000/login/
(5)登陆验证
PerfectCRM/views.py
# PerfectCRM/views.py from django.shortcuts import render,redirect from django.contrib.auth import authenticate,login def acc_login(request): if request.method == \'POST\': username = request.POST.get(\'username\',None) password = request.POST.get(\'password\',None) #user是一个对象 #验证 user = authenticate(username=username,password=password) if user: #登录(已生成session) login(request,user) return redirect(\'/crm/\') return render(request,\'login.html\')
index.html中显示登录的用户名{{request.user}}
<li><a href="https://v3.bootcss.com/examples/dashboard/#">{{ request.user }}</a></li>
(6)登出
Bootstrap3/起步 -->> https://v3.bootcss.com/examples/navbar-static-top/#
右键-->>copy-->>copy element,放到index.html里面
<ul class="nav navbar-nav navbar-right"> <li><a href="https://v3.bootcss.com/examples/dashboard/#"></a></li> <li class="dropdown open"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="true">{{ request.user }} <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">个人信息</a></li> <li><a href="{% url \'logout\' %}">Logout</a></li> </ul> </li> </ul>
PerfectCRM/urls.py
url(r\'^logout/\', views.acc_logout,name=\'logout\'),
PerfectCRM/views.py
def acc_logout(request): logout(request) return redirect("/login/")
现在可以点“logout”跳到login登录界面
<ul class="nav navbar-nav navbar-right"> <li class="dropdown "> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">{{ request.user }} <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">个人信息</a></li> <li><a href="{% url \'logout\' %}">Logout</a></li> </ul> </li> </ul>
(8)添加错误信息
PerfectCRM/views.py
def acc_login(request): error_msg = \'\' if request.method == \'POST\': username = request.POST.get(\'username\',None) password = request.POST.get(\'password\',None) #user是一个对象 #验证 user = authenticate(username=username,password=password) if user: #登录(已生成session) login(request, user) return redirect(\'/crm/\') else: error_msg = \'用户名或密码错误!\' return render(request,\'login.html\',{\'error_msg\':error_msg})
login.html渲染
(9)有的页面只有登录后才能访问
crm/views.py
# crm/views.py from django.shortcuts import render from django.contrib.auth.decorators import login_required @login_required def dashboard(request): return render(request,\'crm/dashboard.html\')
settings中设置如果没登录访问跳转的地方
settings.py
#登录才能访问的页面,如果没登录直接跳转到login界面 LOGIN_URL = \'/login/\'
现在没登录状态访问:http://127.0.0.1:8000/crm/
跳到了login界面
PerfectCRM/views.py
修改acc_login的redirect
#如果有next值就获取next值,没有就跳转到首页 return redirect(request.GET.get(\'next\',\'/\'))
def acc_login(request): error_msg = \'\' if request.method == \'POST\': username = request.POST.get(\'username\',None) password = request.POST.get(\'password\',None) #user是一个对象 #验证 user = authenticate(username=username,password=password) if user: #登录(已生成session) login(request, user) #如果有next值就获取next值,没有就跳转到首页 return redirect(request.GET.get(\'next\',\'/\')) else: error_msg = \'用户名或密码错误!\' return render(request,\'login.html\',{\'error_msg\':error_msg})
没登录状态访问/crm/,跳到login,登录后(获取next=/crm/)跳到/crm/页面
3.3.动态菜单生成
- 首先获取登录的用户(User)
- 通过User反向查找到UsrProfile
- 然后通过UserProfile找到用户关联的所有角色
- 最后通过角色循环遍历出用户所有的菜单
index.html
<ul class="nav nav-sidebar"> <li class="active"><a href="https://v3.bootcss.com/examples/dashboard/#">Overview <span class="sr-only">(current)</span></a></li> <li><a href="https://v3.bootcss.com/examples/dashboard/#">Reports</a></li> <li><a href="https://v3.bootcss.com/examples/dashboard/#">Analytics</a></li> <li><a href="https://v3.bootcss.com/examples/dashboard/#">Export</a></li> {% for role in request.user.userprofile.role.select_related %} {% for menu in role.menus.select_related %} <li><a href="{% if menu.url_type == 0 %}{{ menu.url_name }}{% else %}{% url menu.url_name %}{% endif %}">{{ menu.name }}</a></li> {% endfor %} {% endfor %} </ul>
如果是静态url直接获取,动态url就{% url menu.url_name%}获取
OneToOneField和ForeignKey反向获取
- OneToOneField反向查,直接request.user.userprofile 后面跟反向的表明(小写)就可以
- 如果是FK,直接request.user.userprofile_set 后面跟反向的表明(小写)+“_set” 就可以
- request.user.userprofile.role.select_related等价于request.user.userprofile.role.all
下面开始添加菜单,角色,关联用户
(1)添加菜单
url中name一致
# crm/urls.py from django.conf.urls import url,include from crm import views urlpatterns = [ url(r\'^$\', views.dashboard,name=\'sales_dashboard\'), ]
再添加两个菜单(静态url)
(2)添加角色
添加sales和students两个角色
(3)关联用户
(4)动态菜单查看
现在用不同的角色登录后,就可以实现动态菜单功能了
用derek账户登录(sales的菜单)
用kebi账户登录(students菜单)