CRM客户关系管理系统(二) 第三章、前端页面设计

第三章、前端页面设计

 3.1.前端页面布局

Bootstrap模板下载

 (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.登录页面开发

Bootstrap登录组件

(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菜单)

本章节代码  github下载 (commit4:num2 前端页面布局,登录页面开发和动态菜单设计

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯玄武实验室的专栏

一个 PC上的 “ WormHole ” 漏洞

最近安全界关注的焦点 WormHole 实际是一类不安全的开发习惯所导致的,在 PC 上类似问题也毫不罕见,只不过很多风险被微软默认自带的防火墙缓解了。联想公司...

24600
来自专栏函数式编程语言及工具

Akka(28): Http:About Akka-Http

  众所周知,Akka系统是基于Actor模式的分布式运算系统,非常适合构建大数据平台。所以,无可避免地会出现独立系统之间、与异类系统、与移动系统集成的需求。由...

28170
来自专栏智能大石头

NewLife.Net——网络压测单机1.88亿tps

21840
来自专栏恰童鞋骚年

呼叫中心项目学习总结

这几天一直在学习呼叫中心项目视频教程,跟着学习了下这个项目的一些关键知识点,现在回顾梳理一下重点:

26930
来自专栏HaHack

Speed Up the Rendering Process of hexo 3

11730
来自专栏跟着阿笨一起玩NET

asp.net采用OLEDB方式导入Excel数据时提示:未在本地计算机上注册"Microsoft.Jet.OLEDB.4.0" 提供程序"

 笔者在项目中做做了一个从Excel表格中导入数据的模块、大体上asp.net项目中导入Excel大体分成三类:

82210
来自专栏企鹅号快讯

httpclient接口测试完整用例

本人是在使用httpclient做接口测试的过程中,总结了一些方法,写了一个基本的测试框架。用例的管理和测试结果的保存都放在数据库中,今天算是基本完成和健全了用...

29050
来自专栏云计算

使用Akka HTTP构建微服务:CDC方法

原文地址:https://dzone.com/articles/building-microservices-with-akka-http-a-cdc-appr...

40550
来自专栏Java帮帮-微信公众号-技术文章全总结

学Java-Spring使用Quartz任务调度定时器

睁开眼看一看窗外的阳光,伸一个懒腰,拿起放在床一旁的水白开水,甜甜的味道,晃着尾巴东张西望的猫猫,在窗台上舞蹈。你向生活微笑,生活也向你微笑。 请你不要询问我...

36330
来自专栏智能大石头

NewLife.Net——网络压测单机2266万tps

NewLife.Net压力测试,峰值4.2Gbps,50万pps,消息大小24字节,消息处理速度2266万tps!

14000

扫码关注云+社区

领取腾讯云代金券