Hippo登录展示功能

1. 后台实现登录

1.1 Xadmin

1.11安装

https://github.com/sshwsfc/xadmin.git   #django2
https://github.com/zgljl2012/xadmin-x      #django3
pip install  xadmin-x 

在配置文件中注册如下应用

INSTALLED_APPS = [
    'xadmin',
    'crispy_forms',
    'reversion',
]

# 修改使用中文界面
LANGUAGE_CODE = 'zh-Hans'

# 修改时区
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False

xadmin有建立自己的数据库模型类,需要进行数据库迁移

python manage.py makemigrations
python manage.py migrate

在总路由中添加xadmin的路由信息

import xadmin
xadmin.autodiscover()

# version模块自动注册需要版本控制的 Model
from xadmin.plugins import xversion
xversion.register_models()

urlpatterns = [
    path(r'xadmin/', xadmin.site.urls),
]

如果之前没有创建超级用户,需要创建,如果有了,则可以直接使用之前的。

python manage.py createsuperuser

1.11 给admin配置基本站点

在当前子应用中创建adminx.py,添加如下代码

import xadmin
from xadmin import views

class BaseSetting(object):
    """xadmin的基本配置"""
    enable_themes = True  # 开启主题切换功能
    use_bootswatch = True

xadmin.site.register(views.BaseAdminView, BaseSetting)

class GlobalSettings(object):
    """xadmin的全局配置"""
    site_title = "hippo"  # 设置站点标题
    site_footer = "@追梦nan-2020"  # 设置站点的页脚
    menu_style = "accordion"  # 设置菜单折叠

xadmin.site.register(views.CommAdminView, GlobalSettings)

1.12 注册模型到xadmin中

在xadmin中配置信息

# 轮播图
from .models import BannerInfo
class BannerInfoModelAdmin(object):
    list_display=["name","orders","is_show"]
xadmin.site.register(BannerInfo, BannerInfoModelAdmin)
list_display     #控制列表展示的字段
search_fields    # 控制可以通过搜索框搜索的字段名称,xadmin使用的是模糊查询
list_filter      #可以进行过滤操作的列,对于分类、性别、状态
ordering         #默认排序的字段 
show_detail_fields  #在列表页提供快速显示详情信息
list_editable    #在列表页可以快速直接编辑的字段
refresh_times    #指定列表页的定时刷新
list_export      #控制列表页导出数据的可选格式
show_bookmarks   #控制是否显示书签功能
data_charts      #控制显示图表的样式
data_charts = {  #控制显示图表的样式
        "order_amount": {          #随便写的名称order_amount
          'title': '图书发布日期表',  #控制图标名称
          "x-field": "bpub_date",  #控制x轴字段
          "y-field": ('btitle',),  #控制y轴字段,可以是多个值
          "order": ('id',),        # 控制默认排序
          
        },
model_icon      #控制菜单的图标
readonly_fields #在编辑页面的只读字段
exclude        # 在编辑页面隐藏的字段

1.13 修改xadmin中子应用名

apps.py

class HomeConfig(AppConfig):
    name = 'home'
    verbose_name = '我的首页'

__init__.py

default_app_config = "home.apps.HomeConfig"

1.2 Django_Auth

Django默认已经提供了认证系统。认证系统包含:

- 用户管理
- 权限
- 用户组
- 密码哈希系统
- 用户登录或内容显示的表单和视图
- 一个可插拔的后台系统

Django默认用户的认证机制依赖Session机制,我们在项目中将引入JWT认证机制,将用户的身份凭据存放在Token中,然后对接Django的认证系统,帮助我们来实现:

  • 用户的数据模型
  • 用户密码的加密与验证
  • 用户的权限系统

Django用户模型类

Django认证系统中提供了用户模型类User保存用户的数据,默认的User包含以下常见的基本字段:

字段名 字段描述
username 必选。150个字符以内。 用户名可能包含字母数字,_@+ .-个字符。
first_name 可选(blank=True)。 少于等于30个字符。
last_name 可选(blank=True)。 少于等于30个字符。
email 可选(blank=True)。 邮箱地址。
password 必选。 密码的哈希加密串。 (Django 不保存原始密码)。 原始密码可以无限长而且可以包含任意字符。
groups Group 之间的多对多关系。
user_permissions Permission 之间的多对多关系。
is_staff 布尔值。 设置用户是否可以访问Admin 站点。
is_active 布尔值。 指示用户的账号是否激活。 它不是用来控制用户是否能够登录,而是描述一种帐号的使用状态。
is_superuser 是否是超级用户。超级用户具有所有权限。
last_login 用户最后一次登录的时间。
date_joined 账户创建的时间。 当账号创建时,默认设置为当前的date/time。

常用方法:

  • set_password(raw_password)

    设置用户的密码为给定的原始字符串,并负责密码的。 不会保存User 对象。当Noneraw_password 时,密码将设置为一个不可用的密码。

  • check_password(raw_password)

    如果给定的raw_password是用户的真实密码,则返回True,可以在校验用户密码时使用。

管理器方法:

管理器方法即可以通过User.objects. 进行调用的方法。

  • create_user(username, email=None, password=None, ***extra_fields*)

    创建、保存并返回一个User对象。

  • create_superuser(username, email, password, ***extra_fields*)

    create_user() 相同,但是设置is_staffis_superuserTrue

1.3 创建User应用

python ../../manage.py startapp users

settings.py文件中注册子应用。

# 新增一个系统导包路径
import sys
sys.path.insert(0,os.path.join(BASE_DIR,"apps"))

INSTALLED_APPS = [
		...
  	'users',
]

新增导包路径pycahrm会变黄 将pycahrm设置为源根

1.4 JWT

安装

pip install djangorestframework-jwt

配置

REST_FRAMEWORK = {
    # 异常处理
    'EXCEPTION_HANDLER': 'pippo_api.utils.exceptions.custom_exception_handler',
    #jwt
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}
import datetime
#jwt有效时间
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}

在用户注册或登录成功后,在序列化器中返回用户信息以后同时返回token即可。

1.5 后端登陆认证

Django REST framework JWT提供了登录获取token的视图,可以直接使用

在子应用路由urls.py中

from rest_framework_jwt.views import obtain_jwt_token
from django.urls import path

urlpatterns = [
    path('login/', obtain_jwt_token),
]

在主路由中,引入当前子应用的路由文件

urlpatterns = [
		...
    path('users/', include("users.urls")),
    # include 的值必须是 模块名.urls 格式,字符串中间只能出现一个圆点
]

接下来,我们可以通过postman来测试下功能

1.6 实时刷新token

user中引入refresh_jwt_token

from rest_framework_jwt.views import obtain_jwt_token,refresh_jwt_token
from django.urls import path

urlpatterns = [
    path('login/', obtain_jwt_token),
    path('verify/', refresh_jwt_token), #校监并刷新token
]

修改settings.py配置文件

# JWT
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    'JWT_ALLOW_REFRESH': True,  #这个参数要改True,才能刷新token
}

2. 前端显示页面

1. 添加拦截器

实现每次请求到后端都携带token

新建axios.js文件

import axios from 'axios'

// 配置默认的host,假如你的API host是:http://api.htmlx.club
axios.defaults.baseURL = 'http://127.0.0.1:8000'

// 添加请求拦截器
axios.interceptors.request.use(config=> {
  // 在发送请求之前做些什么
    if(localStorage.token){
        config.headers.Authorization =localStorage.token
    }
  return config
}, error =>  {
  // 对请求错误做些什么
return Promise.reject(error)
});

在main.js中引入

import './axios'

2. 导航守卫

实现每个页面是否携带token,如果存在,那刷新token反之跳转到登录页面

在路由index.js中追加

router.beforeEach((to, from, next) => {
    let token = localStorage.token;
    if (to.path === '/') {
        next()

    } else {

        if (token) {
            axios.post('http://127.0.0.1:8000/users/verify/', {
                token: token,
            }).then((res) => {
                //更新本地的token
                localStorage.token = res.data.token;
            }).catch((error) => {
                //刷新token失败或者token失效
                next('/')
            })
            next()
        } else {
            next('/')
        }
    }
})

3. 登录页组件

Login.vue 样式

<template>
  <div id="box">
    <img src="../assets/login/login.png" alt="">
    <div class="login_box">
      <div class="login-title">
        <p>儒风若梦!</p>
      </div>
      <div class="login-container">
        <div class="title">
          <span>登录</span>
        </div>
        <div class="inp">
          <a-form id="components-form-demo-normal-login" :form="form" @submit="handleSubmit">
            <a-form-item>
              <a-input
                  v-decorator="['username',{ rules: [{ required: true, message: '请输入用户名!' }] },]"
                  placeholder="用户名">
                <a-icon slot="prefix" type="user" style="color: rgba(0,0,0,.25)"/>
              </a-input>
            </a-form-item>
            <a-form-item>
              <a-input v-decorator="['password', { rules: [{ required: true, message: '请输入密码!' }] },]"
                       type="password"
                       placeholder="密码">
                <a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
              </a-input>
            </a-form-item>
            <a-form-item>
              <a-button type="primary" html-type="submit" class="login-form-button">登录</a-button>
            </a-form-item>
          </a-form>
        </div>
      </div>


    </div>
  </div>
</template>

<script>
export default {
  name: "Login",
  beforeCreate() {
    this.form = this.$form.createForm(this, {name: 'normal_login'});
  },
  methods: {
    handleSubmit(e) {
      e.preventDefault();
      this.form.validateFields((err, values) => {
        if (!err) {
          // console.log('Received values of form: ', values);
          //发送ajax请求
          this.axios.post('/users/login/', {
            username: values.username,
            password: values.password
            //获取数据并保存token
          }).then(res => {
            localStorage.token = res.data.token;
            //拿到数据跳转到指定页面
            this.$router.push('/hippo')
          }).catch(error=>{
            this.$message.error('用户名或者密码不对')
          })

            // 首页加载时验证token有效性

        }
      });
    },
  },
}
</script>

<style scoped>
body {
  margin: 0;
}

#box {
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
}

#box img {
  width: 100%;
  height: 100%;
}

.login_box {
  position: absolute;
  width: 600px;
  height: 400px;
  left: 0;
  margin: auto;
  right: 0;
  bottom: 0;
  top: -338px;
}


.login-title {
  width: 100%;
  text-align: center;
}

.login-title p {
  font-family: PingFangSC-Regular;
  font-size: 20px;
  color: #fff;
  letter-spacing: .29px;
  padding-top: 10px;
  padding-bottom: 50px;
}

.login-container {
  width: 550px;
  height: auto;
  background: rgba(255, 255, 255, 0.3);
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
  border-radius: 4px;
  margin: 0 auto;
  padding-bottom: 40px;
}

.title {
  font-size: 22px;
  color: #9b9b9b;
  border-bottom: 1px solid #e6e6e6;
  display: flex;
  justify-content: space-around;
  padding: 20px 60px 10px 60px;
  margin-bottom: 20px;
}

.title span {
  color: #4a4a4a;
}

.inp {
  width: 450px;
  margin: auto;
}


#components-form-demo-normal-login .login-form-button {
  width: 100%;
}


</style>

绑定路由

import Login from '@/views/Login'
{
        path: '/',
        component: Login
    },

4. 导航栏

使用了路由嵌套。

官方文档 https://router.vuejs.org/zh/guide/essentials/nested-routes.html

hippo.vue

<template>
  <a-layout id="components-layout-demo-custom-trigger" style="min-height: 100vh">
    <a-layout-sider v-model="collapsed" :trigger="null" collapsible>
      <!--      <div class="logo" />-->
      <div class="logo" style="color:#fff;font-size: 18px;text-align: center;">
        儒风
      </div>
      <a-menu theme="dark" :default-selected-keys="['1']" mode="inline">
        <template v-for="v in menu_list">

          <a-menu-item :key='v.id' v-if="v.children.length==0">
            <!--            路由指向 点击标签进行跳转到指定的url-->
            <router-link :to="v.menu_url">
              <a-icon :type="v.icon"/>
              <span>{{ v.title }}</span>
            </router-link>
          </a-menu-item>
          <a-sub-menu :key="v.id" v-else>
            <span slot="title"><a-icon :type="v.icon"/><span>{{ v.title }}</span></span>
            <a-menu-item :key="c.id" v-for="c in v.children">
              {{ c.title }}
            </a-menu-item>
          </a-sub-menu>
        </template>

      </a-menu>

    </a-layout-sider>
    <a-layout>
      <a-layout-header style="background: #fff; padding: 0">
        <a-icon
            class="trigger"
            :type="collapsed ? 'menu-unfold' : 'menu-fold'"
            @click="() => (collapsed = !collapsed)"
        />
      </a-layout-header>
      <a-layout-content :style="{ margin: '24px 16px', padding: '5px', minHeight: '280px' }">
        <router-view></router-view>
      </a-layout-content>
      <a-layout-footer style="text-align: center">
        Ant Design ©2018 Created by Ant UED
      </a-layout-footer>
    </a-layout>
  </a-layout>
</template>
<script>
export default {
  name:"Hippo",
  data() {
    return {
      collapsed: false,
      menu_list: [
        {id: 1, icon: 'desktop', title: '控制中心', tube: '', 'menu_url': '/hippo/', children: []},
        {
          id: 2, icon: 'container', title: '主机管理', 'menu_url': '/hippo/host/', children: []
        },
        {
          id: 3, icon: 'bold', title: '批量执行', tube: '', 'menu_url': '/hippo/workbench', children: [
            {id: 10, title: '执行任务', 'menu_url': '/hippo/host/'},
            {id: 11, title: '模版管理', 'menu_url': '/hippo/host/'},
          ]
        },
        {

          id: 4, icon: 'flag', title: '代码发布', tube: '', 'menu_url': '/hippo/workbench', children: [
            {id: 12, title: '应用管理', 'menu_url': '/hippo/release'},
            {id: 13, title: '发布申请', 'menu_url': '/hippo/release'},
          ]
        },
        {id: 5, icon: 'schedule', title: '定时计划', tube: '', 'menu_url': '/hippo/workbench', children: []},
        {
          id: 6, icon: 'deployment-unit', title: '配置中心', tube: '', 'menu_url': '/hippo/workbench', children: [
            {id: 14, title: '环境管理', 'menu_url': '/hippo/environment'},
            {id: 15, title: '服务配置', 'menu_url': '/hippo/workbench'},
            {id: 16, title: '应用配置', 'menu_url': '/hippo/workbench'},
          ]
        },
        {id: 7, icon: 'radar-chart', title: '监控中心', tube: '', 'menu_url': '/hippo/workbench', children: []},
        {
          id: 8, icon: 'alert', title: '报警中心', tube: '', 'menu_url': '/hippo/workbench', children: [
            {id: 17, title: '报警历史', 'menu_url': '/hippo/workbench'},
            {id: 18, title: '报警联系人', 'menu_url': '/hippo/workbench'},
            {id: 19, title: '报警联系组', 'menu_url': '/hippo/workbench'},
          ]
        },
        {
          id: 9, icon: 'setting', title: '系统管理', tube: '', 'menu_url': '/hippo/workbench', children: [
            {id: 20, title: '账户管理', tube: '', 'menu_url': '/hippo/workbench'},
            {id: 21, title: '角色管理', tube: '', 'menu_url': '/hippo/workbench'},
            {id: 22, title: '系统设置', tube: '', 'menu_url': '/hippo/workbench'},
          ]
        },
      ],
    };
  },
};
</script>
<style>
#components-layout-demo-custom-trigger .trigger {
  font-size: 18px;
  line-height: 64px;
  padding: 0 24px;
  cursor: pointer;
  transition: color 0.3s;
}

#components-layout-demo-custom-trigger .trigger:hover {
  color: #1890ff;
}

#components-layout-demo-custom-trigger .logo {
  height: 32px;
  background: rgba(255, 255, 255, 0.2);
  margin: 16px;
}
</style>

5. 仪表盘

home.vue

<template>
  <div class="out">
    <div class="top">
      <!--      栅格-->
      <a-row :gutter="[16,16]">
        <a-col :span="6">
          <!--          卡片-->
          <a-card>
            <!--            js小手-->
            <a href="javascript:void(0);">
              <!--              统计-->
              <a-statistic title="应用" valueStyle="color:green;" style="margin-right: 50px" :value="4" suffix="个">

              </a-statistic>
            </a>

          </a-card>
        </a-col>
        <a-col :span="6">
          <a-card>
            <a href="javascript:void(0);">
              <a-statistic title="主机" valueStyle="color:green;" style="margin-right: 50px" :value="1" suffix="个">
              </a-statistic>
            </a>

          </a-card>
        </a-col>
        <a-col :span="6">
          <a-card>
            <a href="javascript:void(0);">
              <a-statistic title="任务" valueStyle="color:green;" style="margin-right: 50px" :value="2" suffix="个">

              </a-statistic>
            </a>

          </a-card>
        </a-col>
        <a-col :span="6">
          <a-card>
            <a href="javascript:void(0);">
              <a-statistic title="监控" valueStyle="color:green;" style="margin-right: 50px" :value="2" suffix="个">

              </a-statistic>
            </a>

          </a-card>
        </a-col>
      </a-row>
    </div>
    <div class="center" style="margin-top: 30px;">
      <!-- 别忘了给这个标签设置高度,不然看不到效果 -->
      <a-card title="报警趋势">
        <!--        级联标签-->
        <a-cascader placeholder="过滤监控项,默认所有" slot="extra" :options="options" change-on-select @change="onChange"
                    style="width:260px;"/>
        <div>

          <div id="main" style="height: 400px;width:92%;" ref="chart">
            <!--            报警趋势-->

          </div>
        </div>


      </a-card>


    </div>
    <div class="down" style="margin-top: 30px">
      <a-row>
        <a-col :span="14">
          <a-card title="发布申请Top10">
            <a-button type="link" slot="extra">
              今日
            </a-button>
            <a-button type="link" slot="extra">
              本周
            </a-button>
            <a-button type="link" slot="extra">
              本月
            </a-button>
            <a-range-picker @change="onDateChange" slot="extra" style="width: 200px;"/>

            <div id="pub" style="height: 300px;width:94%;" ref="pub">
              <!--                 柱状图-->
            </div>
          </a-card>
        </a-col>
        <a-col :span="9" :offset="1">
          <a-card title="最近30天登录">

            <a-list :data-source="login_list" style="overflow: auto;height: 308px">
              <a-list-item slot="renderItem" slot-scope="item, index">
                {{ item }}
              </a-list-item>

            </a-list>
          </a-card>
        </a-col>
      </a-row>
    </div>


  </div>
</template>
<script>
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN';

export default {
  name: 'Home',
  data() {
    return {
      locale: zhCN,
      login_list: [
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
        '11-18 13:12:02 管理员 通过 1.1.1.1 登录',
      ],
      options: [
        {
          value: 'test',
          label: '站点检测',
          children: [
            {
              value: '1',
              label: 'elatic',

            },
            {
              value: '2',
              label: 'www.baidu.com',

            },
            {
              value: '3',
              label: 'ssh端口',

            },
            {
              value: '4',
              label: 'spug官网',

            },
          ],
        },
        {
          value: 'jiangsu',
          label: '端口检测',
          children: [
            {
              value: 'nanjing',
              label: 'ssh端口',

            },
          ],
        },
        {
          value: 'jiangsu2',
          label: '进程检测',
          children: [
            {
              value: 'nanjing',
              label: 'aaa',

            },
          ],
        },
        {
          value: 'jiangsu3',
          label: '自定义脚本',
          children: [
            {
              value: 'nanjing',
              label: 'top',

            },
          ],
        },
      ],
    };
  },
  methods: {
    onChange(value) {
      console.log(value);
    },
    onDateChange(date, dateString) {
      console.log(date, dateString);
    },


  },
  created() {

    this.$nextTick(() => {
          //console.log(this.$refs);
          // data: ['2019-10-10', '2019-10-11', '2019-10-12', '2019-10-13', '2019-10-14', '2019-10-15', '2019-10-16']
          // 报警图表
          var myChart = this.$echarts.init(this.$refs.chart);
          var alert_option = {
            //
            tooltip: {
              trigger: 'axis'
            },
            color: 'blue',
            grid: {
              left: '3%',
              right: '8%',  // 控制距离左右上下的边距
              bottom: '3%',
              containLabel: true // 完整显示,自动缩放
            },

            xAxis: {
              type: 'category',
              boundaryGap: false,
              data: ['2019-10-10', '2019-10-11', '2019-10-12', '2019-10-13', '2019-10-14', '2019-10-15', '2019-10-16',]
            },
            yAxis: {
              type: 'value'
            },
            series: [
              {
                name: '报警次数',
                type: 'line',
                stack: '总量',
                data: [0, 1, 0, 2, 1, 0, 1],
                smooth: true,
              },

            ]
          };

          // myChart.setOption(alert_option);
          // window.onresize = myChart.resize
          //
          // 发布申请柱状图
          var myChart2 = this.$echarts.init(this.$refs.pub);
          var pub_option = {
            color: ['#3398DB'],
            tooltip: {
              trigger: 'axis',
              axisPointer: {            // 坐标轴指示器,坐标轴触发有效
                type: 'shadow'        // 默认为直线,可选为:'line' | 'shadow'
              }
            },
            grid: {
              left: '3%',
              right: '4%',
              bottom: '3%',
              containLabel: true
            },
            xAxis: [
              {
                type: 'category',
                data: ['订单服务', 'test11', '系统管理', 'test'],
                axisTick: {
                  alignWithLabel: true
                }
              }
            ],
            yAxis: [
              {
                type: 'value'
              }
            ],
            series: [
              {
                name: '直接访问',
                type: 'bar',
                barWidth: '60%',
                data: [10, 52, 200, 334]
              }
            ]
          };
          myChart.setOption(alert_option);
          myChart2.setOption(pub_option);
          window.addEventListener('resize', function () {
            myChart.resize();
            myChart2.resize();
          })


        }
    )
    // 注意:直接通过this.$refs.chart获取该标签不生效,因为vue的标签加载是异步的,可能标签加载还没完成就过去获取这个标签是拿不到的,所以我们需要延迟回调方法来等vue的dom更新完成之后,再获取标签
    // console.log('>>>>', this.$refs, typeof this.$refs.chart)
    // var echarts = require('echarts');


  }

};
</script>

配置路由

    {
        path: '/hippo',
        component: Hippo,
        children:[
            {
                path: '',
                component: Home,
            }
        ]

    },

访问http://localhost:8080/hippo

posted @ 2021-01-20 14:41  追梦nan  阅读(377)  评论(0编辑  收藏  举报