On this page
新增管理员
创建数据迁移表
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 模块
- 安装
shell
npm install crypto --save
- 配置文件配置 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>