技术标签: nodejs 小程序 angularjs mongodb 全栈开发实战之Angularjs管理端 redis 全栈开发实战
上一章我们实现了小黄书小程序的微信授权登录。往下我们打算实现小黄书管理平台端的登录授权操作,毕竟,小黄书后台很早就已经实现了管理员的管理和登录授权逻辑。
小黄书的管理后台我们打算使用Angularjs 2.0的技术来实现,因为笔者自身也是个小白,所以会在学习过程中谈及很多基础的知识。
同时,我们不会从零开始对小黄书的后台进行构建。这样太耗时了,现实中,我们往往是基于一些收费的或者是免费的模板开始的。
Angularjs 2.0的基础知识,本人建议直接看官方的开发指南:
https://angular.cn/docs/ts/latest/guide/
如果前期投入时间不多的话,那么我觉得可以先看介绍Angularjs 2.0架构和重要模块描述的这篇文章:
https://angular.cn/docs/ts/latest/guide/architecture.html
大家之前没有学过Angularjs 2.0的话,本人认为这篇文章是必读的。否则,就算你参照现有的模板,也很难实现任何功能。
小黄书的整个管理后台我们会基于网上的模板来实现。因为网上的大部分模板,无论是付费的还是免费的,都已经帮我们把很多基础设施给搭建好了,这样,我们就不需要做一些重新造轮子的事情了。
比如我们这里选择的ng2-admin模板,它已经帮我们搭建好了如下的一些基础设施:
大家可以到ng2-admin示例网站进行体验。
基于webpack的模板代码,我已经放了一份在github上面:
https://github.com/zhubaitian/XiaoHuangShuPlatform.git
大家直接克隆master分支就好了。我们往后的修改就是基于这份代码的基础上进行的。
以上是ng2-admin的代码组织结构, 从职责和代码组织结构上来分大体分为三层:
第一层 - 应用根模块:由src/app/app.xxx.ts这些文件组成,相当于ng2-admin应用的入口模块。
第二层 - 页面框架层: 由src/app/pages/pages.xxx.ts这些文件组成,主要是实现一个包含菜单栏,footer等的页面框架,如此一来,第三层的视图只需要嵌入到这个框架中就能共享菜单栏等
第三层 - 页面内容层: 由src/app/pages下面的各个文件夹组成,主要是对pages框架内的一个页面的实现,比如dashboard页面。也有例外,比如login和register页面是不在pages框架内的,因为还没有登录,所以不需要有pages框架的菜单栏等。以login为例:
src/index.html是最上层的页面模板。里面的两个标签就是下面的根组件挂载的地方,也就是整个ng2-admin应用开始的位置。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="description" content="<%= htmlWebpackPlugin.options.metadata.description %>">
<% if (webpackConfig.htmlElements.headTags) { %>
<!-- Configured Head Tags -->
<%= webpackConfig.htmlElements.headTags %>
<% } %>
<!-- base url -->
<base href="<%= htmlWebpackPlugin.options.metadata.baseUrl %>">
</head>
<body>
<app>
</app>
<div id="preloader">
<div></div>
</div>
<% if (htmlWebpackPlugin.options.metadata.isDevServer && htmlWebpackPlugin.options.metadata.HMR !== true) { %>
<!-- Webpack Dev Server reload -->
<script src="/webpack-dev-server.js"></script>
<% } %>
</body>
</html>
同时,这个文件也是我们对网站进行访问时候的入口,只是它不是静态的页面,而是根据app标签下面的内容动态调整的。
src/app下面app.xxx.ts文件组成了这个ng2应用的根模块。
我们可以看到入口组件app.component.ts中指定的挂载点就是index.html的app标签。
@Component({
selector: 'app',
template: `
<main [ngClass]="{'menu-collapsed': isMenuCollapsed}" baThemeRun>
<div class="additional-bg"></div>
<router-outlet></router-outlet>
</main>
`
})
export class App {
...
}
这里还要注意app.component.ts的页面模板里面的router-outlet标签,它代表了子路由的占位符。
以ng2-admin为例,二级路由分别有:
这些都是在第二层的src/app/pages/pages.routing.js中进行定义的:
import {
Routes, RouterModule } from '@angular/router';
import {
Pages } from './pages.component';
import {
ModuleWithProviders } from '@angular/core';
// noinspection TypeScriptValidateTypes
// export function loadChildren(path) { return System.import(path); };
export const routes: Routes = [
{
path: 'login',
loadChildren: 'app/pages/login/login.module#LoginModule'
},
{
path: 'register',
loadChildren: 'app/pages/register/register.module#RegisterModule'
},
{
path: 'pages',
component: Pages,
children: [
{
path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{
path: 'dashboard', loadChildren: 'app/pages/dashboard/dashboard.module#DashboardModule' },
{
path: 'editors', loadChildren: 'app/pages/editors/editors.module#EditorsModule' },
{
path: 'components', loadChildren: 'app/pages/components/components.module#ComponentsModule' },
{
path: 'charts', loadChildren: 'app/pages/charts/charts.module#ChartsModule' },
{
path: 'ui', loadChildren: 'app/pages/ui/ui.module#UiModule' },
{
path: 'forms', loadChildren: 'app/pages/forms/forms.module#FormsModule' },
{
path: 'tables', loadChildren: 'app/pages/tables/tables.module#TablesModule' },
{
path: 'maps', loadChildren: 'app/pages/maps/maps.module#MapsModule' }
]
}
];
export const routing: ModuleWithProviders = RouterModule.forChild(routes);
当我们访问其中一个二级路由时,该路由对应的组件就会在父组件(即这里的根组件)router-outlet标签下面进行插入。
比如访问http://localhost:3001/login 时,在router-outlet下面将会插入login这个二级路由关联的登录组件:
访问http://localhost:3001/register 时,在router-outlet下面将会插入register这个二级路由关联的注册组件:
当访问http://localhost:3001/pages 时,根据上面pages.routing.js的二级路由的定义, 会直接跳到http://localhost:3001/pages/dashboard 中:
{
path: 'pages',
component: Pages,
children: [
{
path: '', redirectTo: 'dashboard', pathMatch: 'full' },
...
]
}
此时的router-outlet下面插入的将会是pages对应的组件:
同时,我们看到在pages组件下面,还有一个router-outlet,而下面插入的就是我们的dashboard对应的组件。这时我们就要查看dashboard这个三级路由的父组件pages组件pages.component.ts的代码了:
import {
Component } from '@angular/core';
import {
Routes } from '@angular/router';
import {
BaMenuService } from '../theme';
import {
PAGES_MENU } from './pages.menu';
@Component({
selector: 'pages',
template: `
<ba-sidebar></ba-sidebar>
<ba-page-top></ba-page-top>
<div class="al-main">
<div class="al-content">
<ba-content-top></ba-content-top>
<router-outlet></router-outlet>
</div>
</div>
<footer class="al-footer clearfix">
<div class="al-footer-right" translate>{
{'general.created_with'}} <i class="ion-heart"></i></div>
<div class="al-footer-main clearfix">
<div class="al-copy"> <a href="http://akveo.com" translate>{
{'general.akveo'}}</a> 2016</div>
<ul class="al-share clearfix">
<li><i class="socicon socicon-facebook"></i></li>
<li><i class="socicon socicon-twitter"></i></li>
<li><i class="socicon socicon-google"></i></li>
<li><i class="socicon socicon-github"></i></li>
</ul>
</div>
</footer>
<ba-back-top position="200"></ba-back-top>
`
})
export class Pages {
constructor(private _menuService: BaMenuService,) {
}
ngOnInit() {
this._menuService.updateMenuByRoutes(<Routes>PAGES_MENU);
}
}
原理和根组件的router-outlet是一样的。对于根组件,访问http://localhost:3001/xxx 时,对应的组件会插入到根组件的router-ouetlet组件下面;对于pages组件,访问http://localhost:3001/pages/xxx 时,对应的组件会插入到pages组件中的router-outlet里面,如此类推。
src/app/app.rounting.js定义了应用的上层路由:
import {
Routes, RouterModule } from '@angular/router';
import {
ModuleWithProviders } from '@angular/core';
export const routes: Routes = [
{
path: '', redirectTo: 'pages', pathMatch: 'full' },
{
path: '**', redirectTo: 'pages/dashboard' }
];
export const routing: ModuleWithProviders = RouterModule.forRoot(routes, {
useHash: true });
其中定义了一个路由数组,包含了了两个路由路径:
最后将这个路由数组传入到RouterModule.forRoot来注册我们的路由并export出去给module使用。需要注意的是:
只在根模块路由中调用RouterModule.forRoot。 在其它子功能模块中,我们必须调用RouterModule.forChild方法来注册附属路由。
同时,我们可以在根模块的RouterModule.forRoot的第二个参数中传入一个带有useHash: true的对象,实现hash的url访问方式。如访问login时请注意中间的井号: http://localhost:3001/#/login。
每一个ng2应用都有一个根模块,通常叫做AppModule。如上所述,模块的主要的目的就是将需要用到service,module,component,directive,routing等给整合在一起形成独立的一个模块,同时可以将这个模块export出去给别的模块使用。
此外,根模块还有另外一个职责,就是通过在NgModule修饰器设置bootstrap选项来设置引导组件,也就是我们的根组件。它是所有其它视图的宿主。只有根模块才能设置bootstrap属性
import {
NgModule, ApplicationRef } from '@angular/core';
import {
BrowserModule } from '@angular/platform-browser';
import {
FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
HttpModule } from '@angular/http';
import {
RouterModule } from '@angular/router';
import {
removeNgStyles, createNewHosts, createInputTransfer } from '@angularclass/hmr';
import {
NgbModule } from '@ng-bootstrap/ng-bootstrap';
import {
TranslateService } from '@ngx-translate/core';
/*
* Platform and Environment providers/directives/pipes
*/
import {
ENV_PROVIDERS } from './environment';
import {
routing } from './app.routing';
// App is our top level component
import {
App } from './app.component';
import {
AppState, InternalStateType } from './app.service';
import {
GlobalState } from './global.state';
import {
NgaModule } from './theme/nga.module';
import {
PagesModule } from './pages/pages.module';
// Application wide providers
const APP_PROVIDERS = [
AppState,
GlobalState
];
export type StoreType = {
state: InternalStateType,
restoreInputValues: () => void,
disposeOldHosts: () => void
};
/**
* `AppModule` is the main entry point into Angular2's bootstraping process
*/
@NgModule({
bootstrap: [App],
declarations: [
App
],
imports: [ // import Angular's modules
BrowserModule,
HttpModule,
RouterModule,
FormsModule,
ReactiveFormsModule,
NgaModule.forRoot(),
NgbModule.forRoot(),
PagesModule,
routing
],
providers: [ // expose our Services and Providers into Angular's dependency injection
ENV_PROVIDERS,
APP_PROVIDERS
]
})
export class AppModule {
...
}
以下是在上面提到的官方给出的Angularjs 2.0架构文章的基础上,对这段代码的解析:
Angular 模块(无论是根模块还是特性模块)都是一个带有@NgModule装饰器的类。
装饰器是用来修饰 JavaScript 类的函数。 Angular 有很多装饰器,它们负责把元数据附加到类上,以了解那些类的设计意图以及它们应如何工作。 关于装饰器的更多信息。
NgModule
是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。
bootstrap: 指定应用的主视图(称为根组件),它是所有其它视图的宿主。只有根模块才能设置bootstrap属性。
declarations: 声明本模块中拥有的视图类。Angular 有三种视图类:组件、指令和管道。当在当前模块中需要用到其他component时,我们就需要在本模块的declarations中列出这些模块出来
exports: declarations 的子集,可用于其它模块的组件模板。因为根模块不会从属于任何其他模块来被他人引用,所以我们这里不需要设置任何exports
imports: 本模块声明的组件模板需要用到的类所在的其它模块。比如我们在app.component.ts的模板中需要用到“[ngClass]”来动态设置class属性,所以我们需要引入BrowserModule模块。同时也用到了router-outlet指令,所以需要引入RouterModule模块。
/*
* App Component
* Top Level Component
*/
@Component({
selector: 'app',
template: `
<main [ngClass]="{'menu-collapsed': isMenuCollapsed}" baThemeRun>
<div class="additional-bg"></div>
<router-outlet></router-outlet>
</main>
`
})
export class App {
...
}
由src/app/pages/pages.xxx.ts这些文件组成,主要是实现一个包含菜单栏,footer等的页面框架,如此一来,第三层的视图只需要嵌入到这个框架中就能共享菜单栏等
src/app/pages/pages.component.ts定义了页面框架视图:
import {
Component } from '@angular/core';
import {
Routes } from '@angular/router';
import {
BaMenuService } from '../theme';
import {
PAGES_MENU } from './pages.menu';
@Component({
selector: 'pages',
template: `
<ba-sidebar></ba-sidebar>
<ba-page-top></ba-page-top>
<div class="al-main">
<div class="al-content">
<ba-content-top></ba-content-top>
<router-outlet></router-outlet>
</div>
</div>
<footer class="al-footer clearfix">
<div class="al-footer-right" translate>{
{'general.created_with'}} <i class="ion-heart"></i></div>
<div class="al-footer-main clearfix">
<div class="al-copy"> <a href="http://akveo.com" translate>{
{'general.akveo'}}</a> 2016</div>
<ul class="al-share clearfix">
<li><i class="socicon socicon-facebook"></i></li>
<li><i class="socicon socicon-twitter"></i></li>
<li><i class="socicon socicon-google"></i></li>
<li><i class="socicon socicon-github"></i></li>
</ul>
</div>
</footer>
<ba-back-top position="200"></ba-back-top>
`
})
export class Pages {
constructor(private _menuService: BaMenuService,) {
}
ngOnInit() {
this._menuService.updateMenuByRoutes(<Routes>PAGES_MENU);
}
}
在该页面组件的模板中,定义了整个页面的框架,其中用到的一些结构性directives是通过引入nga.module.ts来获得的。
在nga.module.ts中,引入了以下这些components:
const NGA_COMPONENTS = [
BaAmChart,
BaBackTop,
BaCard,
BaChartistChart,
BaCheckbox,
BaContentTop,
BaFullCalendar,
BaMenuItem,
BaMenu,
BaMsgCenter,
BaMultiCheckbox,
BaPageTop,
BaPictureUploader,
BaSidebar,
BaFileUploader
];
@NgModule({
declarations: [
...NGA_COMPONENTS
],
})
注意declarations中NGA_COMPONENTS数组前面使用了三个点…,代表是对数组展开,这依稀记得好像是es6的语法吧。
我们在pages.component.ts中用到的视图有:
同时,在ba-content-top下方,使用了router-outlet标签。所以所有第三层的视图豆浆从内容标题栏下面开始填充。
最后,在pages类的构造函数中,通过依赖注入的方式引入一个BaMenuService的service,并调用该服务的updateMenuByRoutes方法来根据当前路由来调整菜单的选中。其中参数PAGES_MENU对应pages.menu.ts定义的菜单内容。
src/app/pages/pages.menu.ts定义了我们的菜单项:
export const PAGES_MENU = [
{
path: 'pages',
children: [
{
path: 'dashboard',
data: {
menu: {
title: 'general.menu.dashboard',
icon: 'ion-android-home',
selected: false,
expanded: false,
order: 0
}
}
},
{
path: 'editors',
data: {
menu: {
title: 'general.menu.editors',
icon: 'ion-edit',
selected: false,
expanded: false,
order: 100,
}
},
children: [
{
path: 'ckeditor',
data: {
menu: {
title: 'general.menu.ck_editor',
}
}
}
]
},
...
]
}
];
其中path定义了菜单项对应的路由路径,title定义了菜单项名称。该名称是通过国际化文件src/assets/i18n/US/en.json进行定义的。我们往后的章节说到国际化的时候会阐述,这里我们之需要知道我们可以通过修改这个文件来对菜单项进行一些定制化操作就足够了。
src/app/pages/pages.module.ts 是框架视图的模块定义文件。主要功能是将页面框架视图,需要用到的服务和第三方模块等进行模块化。
比如上面提及的NgaModule模块:
import {
NgModule } from '@angular/core';
import {
CommonModule } from '@angular/common';
import {
routing } from './pages.routing';
import {
NgaModule } from '../theme/nga.module';
import {
AppTranslationModule } from '../app.translation.module';
import {
Pages } from './pages.component';
@NgModule({
imports: [CommonModule, AppTranslationModule, NgaModule, routing],
declarations: [Pages]
})
export class PagesModule {
}
src/app/pages/pages.routing.ts定义了框架视图层的路由。
import {
Routes, RouterModule } from '@angular/router';
import {
Pages } from './pages.component';
import {
ModuleWithProviders } from '@angular/core';
// noinspection TypeScriptValidateTypes
// export function loadChildren(path) { return System.import(path); };
export const routes: Routes = [
{
path: 'login',
loadChildren: 'app/pages/login/login.module#LoginModule'
},
{
path: 'register',
loadChildren: 'app/pages/register/register.module#RegisterModule'
},
{
path: 'pages',
component: Pages,
children: [
{
path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{
path: 'dashboard', loadChildren: 'app/pages/dashboard/dashboard.module#DashboardModule' },
{
path: 'editors', loadChildren: 'app/pages/editors/editors.module#EditorsModule' },
{
path: 'components', loadChildren: 'app/pages/components/components.module#ComponentsModule' },
{
path: 'charts', loadChildren: 'app/pages/charts/charts.module#ChartsModule' },
{
path: 'ui', loadChildren: 'app/pages/ui/ui.module#UiModule' },
{
path: 'forms', loadChildren: 'app/pages/forms/forms.module#FormsModule' },
{
path: 'tables', loadChildren: 'app/pages/tables/tables.module#TablesModule' },
{
path: 'maps', loadChildren: 'app/pages/maps/maps.module#MapsModule' }
]
}
];
export const routing: ModuleWithProviders = RouterModule.forChild(routes);
其中 login, register和pages这三个二级路由对应的视图都会在上面提及的根组件的router-outlet下面进行展开;而pages路由下面的所有三级路由所对应视图,都会在上一小节描述的框架视图中的router-outlet下,也就是在内容标题下面进行展开。
这一层由src/app/pages下面的各个文件夹组成,主要是对pages框架内的一个页面的实现,比如dashboard页面。也有例外,比如login和register页面是不在pages框架内的,因为还没有登录,所以不需要有pages框架的菜单栏等。
毫无例外,这一层也是模块化的,和上面两层一样,同样是由component,routing,module这些ts文件组成。在当前阶段,我们只需要知道它拥有同样的组织方式就好了。
因为我们今后要实现我们自己的页面内容,所以这一层是我们今后工作的重点。我们在往后的章节中会做更详尽的实现和描述。
详细实现请查看github中的代码。
- git clone https://github.com/zhubaitian/XiaoHuangShuPlatform.git
运行:
- npm install
这一系列文章其实我写了有段时间了,后来忙起来忘了发布了。
最后想推下我最近发布的一个小程序:三日清单,希望朋友们能多支持。.
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf