Skip to content
关注公众号,获取新课通知

新增管理员


创建数据迁移表

shell
npx sequelize migration:generate --name=manager

1.执行完命令后,会在 database/migrations/ 目录下生成数据表迁移文件,然后定义

js
"use strict";

module.exports = {
  up: (queryInterface, Sequelize) => {
    const { INTEGER, STRING, DATE, ENUM, TEXT } = Sequelize;
    return queryInterface.createTable("manager", {
      id: {
        type: INTEGER(20),
        primaryKey: true,
        autoIncrement: true,
      },
      username: {
        type: STRING(30),
        allowNull: false,
        defaultValue: "",
        comment: "用户名",
        unique: true,
      },
      password: {
        type: STRING,
        allowNull: false,
        defaultValue: "",
        comment: "密码",
      },
      created_time: DATE,
      updated_time: DATE,
    });
  },

  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable("manager");
  },
};

模型 app\model\manager.js

安装 crypto 模块

  1. 安装
shell
npm install crypto --save
  1. 配置文件配置 config / config.default.js
js
config.crypto = {
  secret: "qhdgw@45ncashdaksh2!#@3nxjdas*_672",
};
js
const crypto = require("crypto");
module.exports = (app) => {
  const { STRING, INTEGER, DATE, ENUM, TEXT } = app.Sequelize;

  const Manager = app.model.define("manager", {
    id: {
      type: INTEGER(20),
      primaryKey: true,
      autoIncrement: true,
    },
    username: {
      type: STRING(30),
      allowNull: false,
      defaultValue: "",
      comment: "用户名",
      unique: true,
    },
    password: {
      type: STRING,
      allowNull: false,
      defaultValue: "",
      comment: "密码",
      set(val) {
        const hmac = crypto.createHash("sha256", app.config.crypto.secret);
        hmac.update(val);
        let hash = hmac.digest("hex");
        this.setDataValue("password", hash);
      },
    },
    created_time: {
      type: DATE,
      get() {
        return app.formatTime(this.getDataValue("created_time"));
      },
    },
    updated_time: DATE,
  });
  return Manager;
};

封装格式化时间方法:app\extend\application.js

js
module.exports = {
  formatTime(time) {
    let d = new Date(time);
    const Month = (d.getMonth() + 1) >= 10
      ? (d.getMonth() + 1)
      : "0" + (d.getMonth() + 1);
    const Day = d.getDate() >= 10 ? d.getDate() : "0" + d.getDate();
    const h = d.getHours() >= 10 ? d.getHours() : "0" + d.getHours();
    const m = d.getMinutes() >= 10 ? d.getMinutes() : "0" + d.getMinutes();
    const s = d.getSeconds() >= 10 ? d.getSeconds() : "0" + d.getSeconds();
    return d.getFullYear() + "-" + Month + "-" + Day + " " + h + ":" + m + ":" +
      s;
  },
};

控制器:app\controller\manager.js

js
	// 新增页面
	async create() {
        const { ctx, app } = this;
		// 渲染公共模板
        await ctx.renderTemplate({
			// 页面标题
            title: "创建管理员",
			// 模板类型 form表单,table表格分页
            tempType: "form",
			// 表单配置
            form: {
                // 提交地址
                action: "/admin/manager",
				// 字段配置
                fields:[{
					label: "用户名",
					type: "text",
					name: "username",
					placeholder: "用户名",
				}, {
					label: "密码",
					type: "text",
					name: "password",
					placeholder: "密码"
				}]
            },
            // 新增成功跳转路径
            successUrl:"/admin/manager"
        })
    }
	// 新增逻辑
    async save() {
        const { ctx, app } = this;

        // 参数验证
        ctx.validate({
            username: {
                type: 'string',
                required: true,
                desc: '用户名'
            },
            password: {
                type: 'string',
                required: true,
                desc: '密码'
            },
        });
        let { username, password } = ctx.request.body;

        // 验证用户是否已经存在
        if (await app.model.Manager.findOne({
            where: { username }
        })) {
            ctx.throw(400, '用户名已存在');
        }
        // 创建用户
        let manager = await app.model.Manager.create({
            username,
            password,
        });
        if (!manager) {
            ctx.throw(400, '创建用户失败');
        }
        ctx.apiSuccess(manager);
    }

context扩展:app\extend\context.js

js
// 渲染公共模板
   async renderTemplate(params = {}) {
	// 获取cookie中的消息提示(闪存)
       let toast = this.cookies.get('toast',{
		// 中文需要解密
           encrypt: true
       });
	// 合并到参数中
       params.toast = toast ? JSON.parse(toast) : null
	// 渲染公共模板
       return await this.render('admin/common/template.html', params)
   },
// 消息提示
toast(msg,type = 'danger'){
	// 设置消息提示到cookie中
       this.cookies.set('toast',JSON.stringify({
           msg,type
       }),{
		// 过期时间
           maxAge: 1500, 
		// 中文需要加密
           encrypt: true
       });
   }

模板部分:app\view\admin\common\template.html

html
<!--引入主布局-->
{% extends "admin/layout/main_app.html" %}
<!--替换页面标题-->
{% block title %} {{ title }} {% endblock %}

模板部分:app\view\admin\layout\main_app.html

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
    <title>{% block title %}后台{% endblock %} - 直播后台</title>
    <!-- Favicon -->
    <link rel="shortcut icon" type="image/x-icon" href="/public/assets/img/favicon.png">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="http://cdn.bootstrapmb.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <!-- Fontawesome CSS -->
    <link rel="stylesheet" href="/public/assets/css/font-awesome.min.css">
    <!-- Feathericon CSS -->
    <link rel="stylesheet" href="/public/assets/css/feathericon.min.css">
    <!-- Main CSS -->
    <link rel="stylesheet" href="/public/assets/css/style.css">
    <!--[if lt IE 9]>
			<script src="/public/assets/js/html5shiv.min.js"></script>
			<script src="/public/assets/js/respond.min.js"></script>
        <![endif]-->
    {% block css %}{% endblock %}
    <style>
        [v-cloak] {
            display: none;
        }

        .fade-enter-active,
        .fade-leave-active {
            transition: opacity .3s;
        }

        .fade-enter,
        .fade-leave-to

        /* .fade-leave-active below version 2.1.8 */
            {
            opacity: 0;
        }
    </style>
    <!-- jQuery -->
    <script src="/public/assets/js/jquery-3.2.1.min.js"></script>

    <!-- Bootstrap Core JS -->
    <script src="/public/assets/js/popper.min.js"></script>
    <script src="http://cdn.bootstrapmb.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>

    <!-- Slimscroll JS -->
    <script src="/public/assets/plugins/slimscroll/jquery.slimscroll.min.js"></script>

    <!-- Custom JS -->
    <script src="/public/assets/js/script.js"></script>

    <script src="/public/assets/js/vue.min.js"></script>
    <script src="/public/assets/js/vue.components.js"></script>
</head>

<body>

    <!-- Main Wrapper -->
    <div class="main-wrapper" id="vueapp">
        <confirm ref="confirm"></confirm>
        <toast ref="toast"></toast>
        <!-- 公共头部 -->
        {% include "admin/layout/_header.html" %}
        <!-- /公共头部 -->

        <!-- 公共侧边栏 -->
        {% include "admin/layout/_sidebar.html" %}
        <!-- /公共侧边栏 -->

        <!-- 页面主布局 -->
        {% block pagewrapper %}
        <div class="page-wrapper">
            <div class="content container-fluid">
                <div class="page-header">
                    <div class="row align-items-center">
                        <div class="col">
                            <h3 class="page-title">{{ title }}</h3>
                            <ul class="breadcrumb">
                                <li class="breadcrumb-item"><a href="/admin">后台首页</a></li>
                                <li class="breadcrumb-item active">{{ title }}</li>
                            </ul>
                        </div>
                    </div>
                </div>
                

                <div class="row">
                    <div class="col-sm-12">
						<!--根据不同的模板类型渲染不同模板-->
                        {% if tempType === 'form' %}
                        {% include "admin/layout/_form.html" %}
                        {% else %}
                        {% include "admin/layout/_table.html" %}
                        {% endif %}

                    </div>
                </div>
            </div>
        </div>
        {% endblock %}
        <!-- /页面主布局 -->

    </div>
    <!-- /Main Wrapper -->
    {% block js %}{% endblock %}
    {% if toast %}
    <script>
		// 消息提示
        if(Vueapp && Vueapp.$refs.toast){
            Vueapp.$refs.toast.show({
                msg: "{{toast.msg}}",
                type:"{{toast.type}}",
            })
        }
    </script>
    {% endif %}
</body>

</html>

模板部分(公共头部):app\view\admin\layout\ _header.html

html
<div class="header">

    <!-- Logo -->
    <div class="header-left">
        <a href="/admin" class="logo">
            <img src="/public/assets/img/logo (1).png" alt="Logo">
        </a>
        <a href="/admin" class="logo logo-small">
            <img src="/public/assets/img/logo-small.png" alt="Logo" width="30" height="30">
        </a>
    </div>
    <!-- /Logo -->

    <a href="javascript:void(0);" id="toggle_btn">
        <i class="fe fe-text-align-left"></i>
    </a>

    <!-- <div class="top-nav-search">
        <form>
            <input type="text" class="form-control" placeholder="搜索直播间">
            <button class="btn" type="submit"><i class="fa fa-search"></i></button>
        </form>
    </div> -->

    <!-- Mobile Menu Toggle -->
    <a class="mobile_btn" id="mobile_btn">
        <i class="fa fa-bars"></i>
    </a>
    <!-- /Mobile Menu Toggle -->

    <!-- Header Right Menu -->
    <ul class="nav user-menu">
        <!-- User Menu -->
        <li class="nav-item dropdown has-arrow">
            <a href="#" class="dropdown-toggle nav-link" data-toggle="dropdown">
                <span class="user-img"><img class="rounded-circle" src="/public/assets/img/profiles/avatar-01.jpg"
                        width="31"></span>
            </a>
            {% if ctx.session.auth %}
            <div class="dropdown-menu">
                <div class="user-header">
                    <div class="avatar avatar-sm">
                        <img src="/public/assets/img/profiles/avatar-01.jpg" class="avatar-img rounded-circle">
                    </div>
                    <div class="user-text">
                        <h6>{{ctx.session.auth.username}}</h6>
                        <p class="text-muted mb-0">超级管理员</p>
                    </div>
                </div>
                <a class="dropdown-item" href="/admin/logout">退出登录</a>
            </div>
            {% endif %}
        </li>
        <!-- /User Menu -->

    </ul>
    <!-- /Header Right Menu -->

</div>

模板部分(公共侧边):app\view\admin\layout\ _sidebar.html

html
<div class="sidebar" id="sidebar">
    <div class="sidebar-inner slimscroll">
        <div id="sidebar-menu" class="sidebar-menu">
            <ul>
                {% for item in ctx.locals.sidebar %}
                <li class="{{ item.active }}">
                    <a href="{{item.url}}"><i class="fe {{item.icon}}"></i> <span>{{item.name}}</span></a>
                </li>
                {% endfor %}
            </ul>
        </div>
    </div>
</div>

模板部分(公共表格):app\view\admin\layout\ _table.html

html
<div class="card card-table">
    {% if table.buttons %}
    <div class="card-header">
        {% if table.buttons.add %}
        <a class="btn btn-outline-primary" href="{{ table.buttons.add }}">新增</a>
        {% endif %}
    </div>
    {% endif %}
    <div class="card-body">

        {% if table.tabs %}
        <ul class="nav nav-tabs nav-tabs-top">
            {% for item in table.tabs %}
            <li class="nav-item"><a class="nav-link {% if item.active %}active{% endif %}" href="{{ item.url }}">{{ item.name }}</a></li>
            {% endfor %}
        </ul>
        {% endif %}

        <div class="table-responsive">
            <table class="table table-hover table-center mb-0">
                <thead>
                    <tr>
                        {% for item in table.columns %}
                        <th {% if item.fixed %} class="text-{{item.fixed}}" {% endif %} {% if item.width %}
                            width="{{item.width}}" {% endif %}>{{item.title}}</th>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody>
                    {% if table.data %}
                    {% for item in table.data %}
                    <tr>
                        {% for c in table.columns %}
                        <td {% if c.fixed %} class="text-{{c.fixed}}" {% endif %}>
                            {% if c.key %}
                            {{ item[c.key] }}
                            {% elif c.action %}
                            <div class="actions">
                                {% if c.action.edit %}
                                <a href="{{c.action.edit(item.id) }}" class="btn btn-sm bg-success-light mr-2">修改</a>
                                {% endif %}
                                {% if c.action.delete %}
                                <a @click="del('{{c.action.delete(item.id) }}')" class="btn btn-sm bg-danger-light mr-2">删除</a>
                                {% endif %}
                            </div>
                            {% elif c.render %}
                            {{ c.render(item) | safe}}
                            {% endif %}
                        </td>
                        {% endfor %}
                    </tr>
                    {% endfor %}
                    {% endif %}
                </tbody>
            </table>
        </div>
    </div>

    <div class="card-footer d-flex justify-content-center">
        {{ ctx.locals.pageRender | safe }}
    </div>

</div>
<script>
    var Vueapp = new Vue({
        el: '#vueapp',
        data: function () {
            return {
                
            }
        },
        methods: {
            del(url){
                this.$refs.confirm.show({
                    content:"是否要删除该记录?",
                    success:function(){
                        window.location.href = url
                    }
                })
            },
            modal(url,content){
                this.$refs.confirm.show({
                    content,
                    success:function(){
                        window.location.href = url
                    }
                })
            },
            openInfo(url,title){
                var _t = this
                $.ajax({
                    type: "GET",
                    contentType: "application/json;charset=UTF-8",
                    url,
                    success: function (result) {
                        console.log(result)
                        _t.$refs.confirm.show({
                            isconfirm:false,
                            ths:result.data.ths,
                            data:result.data.data,
                            title
                        })
                    },
                    error: function (e) {
                        _t.$refs.toast.show({
                            msg: e.responseJSON.data
                        })
                    }
                });
            }
        },
    })
</script>

模板部分(公共表单):app\view\admin\layout\ _form.html

html
<div class="card">
    <div class="card-body">
        {% if form %}
        <form v-cloak action="{{ form.action }}" method="{{ form.method if form.method else 'POST' }}">
            {% for item in form.fields %}
            <div class="form-group row">
                <label class="col-form-label col-md-2">{{item.label}}</label>
                <div class="col-md-10">
                    {% if item.name === 'coin' %}
                    <div class="input-group">
                        <div class="input-group-prepend">
                            <span class="input-group-text">¥</span>
                        </div>
                        <input class="form-control" type="{{item.type}}" name="{{item.name}}" v-model="form.{{ item.name }}">
                    </div>
                    {% else %}
                        {% if item.type === 'file' %}
                        <input type="file" name="{{item.name}}" class="form-control" @change="fileUpload($event,'{{item.name}}')">
                        <img v-if="form['{{item.name}}']" :src="form['{{item.name}}']" class="avatar-lg border rounded p-1 mt-2">
                        {% else %}
                        <input type="{{item.type}}" name="{{item.name}}" class="form-control"
                        placeholder="{{item.placeholder}}" v-model="form.{{ item.name }}">
                        {% endif %}
                    {% endif %}
                </div>
            </div>
            {% endfor %}
            <div class="text-right mt-3"> <button type="submit" class="btn btn-primary" @click.stop.prevent="submit">提
                    交</button>
            </div>
        </form>
        {% endif %}

    </div>
</div>
<script>
    var Vueapp = new Vue({
        el: '#vueapp',
        data: function () {
            return {
                form: {
                    {% for item in form.fields %} {{ item.name }}: "{{ form.data[item.name] if form.data[item.name] else item.default }}",{% endfor %}
                }
            }
        },
        methods: {
            fileUpload:function(event,name){
                var _t = this
                // 上传文件
                let file = event.target.files
                let formData = new FormData()
                formData.append('file', file[0])

                $.ajax({
                    url:"/admin/upload?_csrf={{ctx.csrf|safe}}",
                    type:'POST',
                    data: formData,
                    contentType: false,
                    processData: false,
                    success:function(res){
                        _t.form[name] = res.data.url
                        _t.$refs.toast.show({
                            msg: '上传成功',
                            type:"success"
                        })
                    }
                })
            },
            submit: function () {
                var _t = this
                $.ajax({
                    type: "{{ form.method if form.method else 'POST' }}",
                    contentType: "application/json;charset=UTF-8",
                    url: "{{ form.action }}?_csrf={{ctx.csrf|safe}}",
                    data: JSON.stringify(this.form),
                    success: function (result) {
                        _t.$refs.toast.show({
                            msg: "{{ '修改' if id else '创建' }}成功",
                            type:"success",
                            success:function(){
                                window.location.href = "{{successUrl}}"
                            },
                            delay:1000
                        })
                    },
                    error: function (e) {
                        _t.$refs.toast.show({
                            msg: e.responseJSON.data
                        })
                    }
                });
            }
        },
    })
</script>