修改路径

This commit is contained in:
小海
2020-05-16 22:18:45 +08:00
parent abc792a561
commit 42177a7721
683 changed files with 92 additions and 18398 deletions

View File

@@ -0,0 +1,41 @@
<div class="inner-content">
<nz-card nzTitle="文章管理" nzSize="small">
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="page"
[nzPageSize]="pageSize" [nzLoading]="loading"
(nzPageIndexChange)="getArticle()" nzFrontPagination="false">
<thead>
<th>标题</th>
<th>发布日期</th>
<th>更新日期</th>
<th>文章类型</th>
<th>阅读量</th>
<th>是否可见</th>
<th>操作</th>
</thead>
<tbody>
<tr *ngFor="let data of table.data">
<td nz-typography nzEllipsis="true" [nzTooltipTitle]="data.title" nzTooltipPlacement="right"
nz-tooltip>{{data.title}}</td>
<td>{{data.publishDateFormat}}</td>
<td>{{data.updateDateFormat}}</td>
<td>
<nz-tag nzColor="green" *ngIf="data.original">原创</nz-tag>
<nz-tag nzColor="#ff5500" *ngIf="!data.original">转载</nz-tag>
</td>
<td>
<nz-tag [nzColor]="'purple'">{{data.readingNumber}}</nz-tag>
</td>
<td><input type="checkbox" [checked]="data.open" disabled></td>
<td>
<a routerLink="/write" [queryParams]="{id:data.id}" class="edit-opr">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a [routerLink]="'/article'+data.id" class="show-opr">查看</a>
<nz-divider nzType="vertical"></nz-divider>
<a nz-popconfirm nzPopconfirmTitle="确定要删除这篇文章吗?" nzOkText="删除" nzCancelText="点错了"
(nzOnConfirm)="deleteArticle(data.id)" class="del-opr">删除</a>
</td>
</tr>
</tbody>
</nz-table>
</nz-card>
</div>

View File

@@ -0,0 +1,3 @@
td {
max-width: 200px;
}

View File

@@ -0,0 +1,50 @@
import {Component, OnInit} from '@angular/core';
import {NzMessageService} from 'ng-zorro-antd';
import {ApiService} from '../../../api/api.service';
import {PageList} from '../../../class/HttpReqAndResp';
import {Article} from '../../../class/Article';
import {Title} from '@angular/platform-browser';
@Component({
selector: 'app-admin-article',
templateUrl: './admin-article.component.html',
styleUrls: ['./admin-article.component.less']
})
export class AdminArticleComponent implements OnInit {
constructor(private apiService: ApiService, private nzMessage: NzMessageService, private title: Title) {
}
page: number = 1;
pageSize: number = 10;
pageList: PageList<Article> = new PageList<Article>();
loading: boolean = true;
ngOnInit(): void {
this.title.setTitle('小海博客 | 文章管理')
this.getArticle();
}
getArticle = () => this.apiService.adminArticles(this.page, this.pageSize).subscribe({
next: data => this.pageList = data.result,
complete: () => this.loading = false,
error: err => this.loading = false
})
deleteArticle(id) {
this.loading = true;
this.apiService.deleteArticle(id).subscribe({
next: data => {
this.nzMessage.success('删除成功')
this.loading = false;
this.getArticle();
},
error: err => {
this.nzMessage.error(err.msg)
this.loading = false
}
})
}
}

View File

@@ -0,0 +1,31 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {AdminArticleComponent} from './admin-article.component';
import {
NzCardModule,
NzDividerModule,
NzPopconfirmModule,
NzTableModule, NzTagModule,
NzToolTipModule,
NzTypographyModule
} from 'ng-zorro-antd';
@NgModule({
declarations: [
AdminArticleComponent
],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: AdminArticleComponent}]),
NzTableModule,
NzTypographyModule,
NzToolTipModule,
NzCardModule,
NzDividerModule,
NzPopconfirmModule,
NzTagModule,
]
})
export class AdminArticleModule {
}

View File

@@ -0,0 +1,42 @@
<div class="inner-content">
<nz-card nzTitle="评论管理" nzSize="small">
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
[nzPageSize]="pageSize" [nzLoading]="loading"
(nzPageIndexChange)="getComment()" nzFrontPagination="false">
<thead>
<th>文章标题</th>
<th>评论内容</th>
<th>评论者</th>
<th>评论日期</th>
<th>操作</th>
</thead>
<tbody>
<tr *ngFor="let data of table.data">
<td nz-typography nzEllipsis="true" [nzTooltipTitle]="data.articleTitle" nzTooltipPlacement="right"
nz-tooltip>{{data.articleTitle}}</td>
<td nz-typography nzEllipsis="true" [nzTooltipTitle]="data.content" nzTooltipPlacement="right"
nz-tooltip style="min-width: 100px;max-width: 400px">
<span *ngIf="!editInfo.editFocus||data.id!==editInfo.id">{{data.content}}</span>
<nz-input-group *ngIf="editInfo.editFocus&&data.id===editInfo.id"
[nzPrefix]="tagIcon" style="width: 50%" (blur)="editInfo.editFocus=false">
<input type="text" nz-input [(ngModel)]="editInfo.content.content" nzSize="small"
[autofocus]="editInfo.editFocus&&data.id===editInfo.id"
(keyup.enter)="edit()">
<button nz-button (click)="edit()" nzSize="small">更新</button>
<button nz-button (click)="editInfo.editFocus=false" nzSize="small">取消</button>
</nz-input-group>
</td>
<td>{{data.authorName}}</td>
<td>{{data.date}}</td>
<td>
<a (click)="editFocus(data)" class="edit-opr">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a nz-popconfirm nzPopconfirmTitle="确定要删除这篇文章吗?" nzOkText="删除" nzCancelText="点错了"
(nzOnConfirm)="deleteComment(data.id)" class="del-opr">删除</a>
</td>
</tr>
</tbody>
</nz-table>
</nz-card>
</div>
<ng-template #tagIcon><i nz-icon nzType="message" nzTheme="outline"></i></ng-template>

View File

@@ -0,0 +1,3 @@
td {
max-width: 300px;
}

View File

@@ -0,0 +1,102 @@
import {Component, OnInit} from '@angular/core';
import {NzMessageService} from 'ng-zorro-antd';
import {ApiService} from '../../../api/api.service';
import {PageList} from '../../../class/HttpReqAndResp';
import {Comment, CommentReq} from '../../../class/Comment';
import {GlobalUserService} from '../../../services/global-user.service';
@Component({
selector: 'app-admin-comment',
templateUrl: './admin-comment.component.html',
styleUrls: ['./admin-comment.component.less']
})
export class AdminCommentComponent implements OnInit {
constructor(private apiService: ApiService, private messageService: NzMessageService, private userService: GlobalUserService) {
this.userService.watchUserInfo({
next: data => {
if (data.result) {
if (data.result.role === 'admin') {
this.getComment = this.getCommentForAdmin;
} else {
this.getComment = this.getCommentForUser;
}
} else {
this.getComment = this.getCommentForUser;
}
this.getComment()
},
error: null,
complete: null
})
}
loading: boolean = true;
pageIndex: number = 1;
pageSize: number = 10;
pageList: PageList<Comment> = new PageList<Comment>();
editInfo = {
id: null,
content: new CommentReq(true),
editFocus: false,
}
getComment: any;// 存放获取评论的方法
ngOnInit(): void {
}
getCommentForAdmin = () => this.apiService.getCommentByTypeForAdmin(true, this.pageIndex, this.pageSize).subscribe({
next: data => this.pageList = data.result,
complete: () => this.loading = false,
error: err => this.loading = false
})
getCommentForUser = () => this.apiService.getCommentByTypeForUser(true, this.pageIndex, this.pageSize).subscribe({
next: data => this.pageList = data.result,
complete: () => this.loading = false,
error: err => this.loading = false
})
deleteComment(id: number) {
this.loading = true;
this.apiService.deleteComment(id).subscribe({
next: () => {
this.messageService.success('删除评论成功');
this.getComment();
},
error: err => {
this.loading = false;
this.messageService.error(err.msg);
},
complete: () => this.loading = false
})
}
edit() {
this.editInfo.editFocus = false;
this.loading = true;
this.apiService.updateComment(this.editInfo.content).subscribe({
next: data => {
this.messageService.success('更新评论成功');
this.getComment();
},
error: err => {
this.loading = false;
this.messageService.success(err.msg);
},
complete: () => this.loading = false
})
}
editFocus(data: Comment) {
this.editInfo.id = data.id;
this.editInfo.content.content = data.content;
this.editInfo.content.id = data.id;
this.editInfo.content.articleID = data.articleID;
this.editInfo.content.pid = data.pid;
this.editInfo.content.responseId = data.responseId;
this.editInfo.editFocus = true;
}
}

View File

@@ -0,0 +1,37 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {AdminCommentComponent} from './admin-comment.component';
import {
NzButtonModule,
NzCardModule,
NzDividerModule, NzIconModule, NzInputModule,
NzPopconfirmModule,
NzTableModule,
NzToolTipModule,
NzTypographyModule
} from 'ng-zorro-antd';
import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [
AdminCommentComponent
],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: AdminCommentComponent}]),
NzCardModule,
NzTableModule,
NzDividerModule,
NzPopconfirmModule,
NzTypographyModule,
NzToolTipModule,
NzInputModule,
FormsModule,
NzIconModule,
NzButtonModule
]
})
export class AdminCommentModule {
}

View File

@@ -0,0 +1,67 @@
<div *ngIf="userInfo&&userInfo.role==='admin'">
<div nz-row>
<nz-card nzTitle="统计" nz-col nzSpan="12" nzSize="small">
<nz-row [nzGutter]="24">
<nz-col [nzSpan]="6">
<nz-statistic [nzValue]="(counts.articleCount | number)!" nzTitle="文章数量"></nz-statistic>
</nz-col>
<nz-col [nzSpan]="6">
<nz-statistic [nzValue]="(counts.tagCount | number)!" nzTitle="标签量"></nz-statistic>
</nz-col>
<nz-col [nzSpan]="6">
<nz-statistic [nzValue]="(counts.categoryCount | number)!" nzTitle="分类数量"></nz-statistic>
</nz-col>
<nz-col [nzSpan]="6">
<nz-statistic [nzValue]="(counts.commentCount | number)!" nzTitle="评论量"></nz-statistic>
</nz-col>
</nz-row>
</nz-card>
<nz-card nzTitle="信息" nz-col nzSpan="11" nzOffset="1" nzSize="small">
<nz-row [nzGutter]="24">
<nz-col [nzSpan]="8">
<nz-statistic [nzValue]="(counts.visitorCount | number)!" nzTitle="总访问量"></nz-statistic>
</nz-col>
<nz-col [nzSpan]="8">
<nz-statistic [nzValue]="(dayVisitCount | number)!" nzTitle="日访问量"></nz-statistic>
</nz-col>
<nz-col [nzSpan]="8">
<nz-statistic [nzValue]="userInfo.recentlyLandedDate?userInfo.recentlyLandedDate:''"
nzTitle="上次登录"></nz-statistic>
</nz-col>
</nz-row>
</nz-card>
</div>
<div nz-row style="margin-top: 30px">
<nz-card style="width:100%;" nzSize="small" nzTitle="日志" [nzExtra]="reload">
<ng-template #reload>
<a (click)="getLog()" title="刷新"><i nz-icon nzType="reload" nzTheme="outline"></i></a>
</ng-template>
<nz-spin [nzSpinning]="logLoading" style="width: 100%;">
<pre style="width: 100%;max-height: 500px;overflow: auto;">{{logText}}</pre>
</nz-spin>
</nz-card>
</div>
</div>
<div *ngIf="userInfo&&userInfo.role==='user'">
<div nz-row>
<nz-card nzTitle="信息" nz-col nzSpan="6" nzOffset="1" nzSize="small">
<nz-statistic [nzValue]="userInfo.recentlyLandedDate?userInfo.recentlyLandedDate:''"
nzTitle="上次登录"></nz-statistic>
</nz-card>
<nz-card nzTitle="关于此博客" nzSize="small" nz-col nzSpan="6" nzOffset="2">
<p>此博客由 <a href="https://github.com/xiaohai2271" target="_blank">禾几海(郑海)</a> 设计并实现的</p>
<p>博客自2019年3月开始开发编写 5月开始正式运行至今</p>
<p>博客所有代码都是开源的,你可以随意修改,运行和发布</p>
<p>
<a href="https://github.com/xiaohai2271/blog-backEnd" target="_blank">后端代码可以在此处找到</a>
<nz-divider nzType="vertical"></nz-divider>
<a href="https://github.com/xiaohai2271/blog-frontEnd" target="_blank">前端代码可以在此处找到</a>
</p>
<p>如果觉得博客还不错请前往Github支持我给我点一个star吧</p>
</nz-card>
<nz-card nz-col nzSpan="6" nzOffset="2">
<p style="font-style: italic">坚强的信心,能使平凡的人做出惊人的事业。</p>
<p style="text-align: right"> ——马尔顿</p>
</nz-card>
</div>
</div>

View File

@@ -0,0 +1,61 @@
import {Component, OnInit} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {ApiService} from '../../../api/api.service';
import {GlobalUserService} from '../../../services/global-user.service';
import {User} from '../../../class/User';
@Component({
selector: 'app-admin-dashboard',
templateUrl: './admin-dashboard.component.html',
styleUrls: ['./admin-dashboard.component.less']
})
export class AdminDashboardComponent implements OnInit {
constructor(private apiService: ApiService, private userService: GlobalUserService, private http: HttpClient) {
this.getUserInfo();
}
logLoading: boolean = true;
logText: string = null;
counts: {
articleCount: number,
visitorCount: number,
categoryCount: number,
leaveMsgCount: number,
tagCount: number,
commentCount: number
} = {articleCount: 0, visitorCount: 0, categoryCount: 0, tagCount: 0, commentCount: 0, leaveMsgCount: 0}
dayVisitCount: number = 0;
userInfo: User = new User();
ngOnInit(): void {
}
getLog() {
this.http.get('https://api.celess.cn/blog.log', {responseType: 'text'}).subscribe(data => {
this.logText = data;
this.logLoading = false
});
}
getCounts = () => this.apiService.counts().subscribe({
next: data => this.counts = data.result
})
getDayVisitCount = () => this.apiService.dayVisitCount().subscribe({
next: data => this.dayVisitCount = data.result
})
getUserInfo = () => this.userService.watchUserInfo({
next: data => {
this.userInfo = data.result
if (data.result && data.result.role === 'admin') {
this.getLog();
this.getCounts();
this.getDayVisitCount();
}
},
error: null,
complete: null
})
}

View File

@@ -0,0 +1,31 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AdminDashboardComponent} from './admin-dashboard.component';
import {RouterModule} from '@angular/router';
import {
NzButtonModule,
NzCardModule,
NzDividerModule,
NzGridModule,
NzIconModule,
NzSpinModule,
NzStatisticModule
} from 'ng-zorro-antd';
@NgModule({
declarations: [AdminDashboardComponent],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: AdminDashboardComponent}]),
NzGridModule,
NzCardModule,
NzButtonModule,
NzSpinModule,
NzIconModule,
NzStatisticModule,
NzDividerModule
]
})
export class AdminDashboardModule {
}

View File

@@ -0,0 +1,62 @@
<div class="inner-content">
<nz-card nzTitle="友链管理" nzSize="small">
<button nz-button (click)="addLink()" style="margin-bottom: 15px">新增</button>
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
[nzPageSize]="pageSize" [nzLoading]="loading"
(nzPageIndexChange)="getLinks()" nzFrontPagination="false">
<thead>
<th>友链名称</th>
<th>友链地址</th>
<th>是否可见</th>
<th>操作</th>
</thead>
<tbody>
<tr *ngFor="let data of table.data">
<td>{{data.name}}</td>
<td><a [href]="data.url" target="_blank">{{data.url}}</a></td>
<td>
<input type="checkbox" disabled [checked]="data.open">
</td>
<td>
<a (click)="showEdit(data)" class="edit-opr">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a nz-popconfirm nzPopconfirmTitle="确定要删除这条友链吗?" nzOkText="删除" nzCancelText="点错了"
(nzOnConfirm)="delete(data.id)" class="del-opr">删除</a>
</td>
</tr>
</tbody>
</nz-table>
</nz-card>
</div>
<nz-modal [(nzVisible)]="modalVisible" [nzTitle]="modalTitle" (nzOnOk)="modalConfirm()"
(nzOnCancel)="modalVisible = false" [nzClosable]="true" [nzOkDisabled]="!formGroup.valid">
<form nz-form [formGroup]="formGroup">
<nz-form-item>
<nz-form-label nzRequired>网站名称</nz-form-label>
<nz-form-control nzErrorTip="网站名称不可为空">
<input nz-input formControlName="name">
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzRequired>网站链接</nz-form-label>
<nz-form-control [nzErrorTip]="nameErrTip">
<input nz-input formControlName="url">
<ng-template #nameErrTip>
<div *ngIf="formGroup.controls.url.hasError('required')">网站链接不可为空</div>
<div *ngIf="formGroup.controls.url.hasError('pattern')">网站链接格式不正确</div>
<div></div>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzRequired>是否公开</nz-form-label>
<nz-form-control nzErrorTip="不可为空">
<nz-select nzPlaceHolder="请选择" formControlName="open" [nzAllowClear]="true">
<nz-option [nzValue]="true" nzLabel="公开"></nz-option>
<nz-option [nzValue]="false" nzLabel="不公开"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
</form>
</nz-modal>

View File

@@ -0,0 +1,91 @@
import {Component, OnInit} from '@angular/core';
import {PageList, Response} from '../../../class/HttpReqAndResp';
import {Link} from '../../../class/Link';
import {ApiService} from '../../../api/api.service';
import {NzMessageService} from 'ng-zorro-antd';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {Observable} from 'rxjs';
@Component({
selector: 'app-admin-link',
templateUrl: './admin-link.component.html',
styleUrls: ['./admin-link.component.less']
})
export class AdminLinkComponent implements OnInit {
constructor(private apiService: ApiService, private messageService: NzMessageService) {
this.formGroup = new FormGroup({
id: new FormControl(null),
name: new FormControl(null, [Validators.required]),
url: new FormControl(null, [Validators.required, Validators.pattern(/^(https:\/\/|http:\/\/|)([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$/)]),
open: new FormControl(null, [Validators.required]),
oper: new FormControl(null)
})
}
pageList: PageList<Link> = new PageList<Link>();
loading: boolean = true;
pageIndex: number = 1;
pageSize: number = 10;
modalVisible: boolean = false;
modalTitle: string = '';
formGroup: FormGroup;
getLinks = () => this.apiService.adminLinks(this.pageSize, this.pageIndex).subscribe({
next: data => this.pageList = data.result,
error: () => this.loading = false,
complete: () => this.loading = false,
})
ngOnInit(): void {
this.getLinks();
}
delete(id: number) {
this.apiService.deleteLink(id).subscribe({
next: data => {
this.messageService.success('删除成功');
this.getLinks();
},
error: () => {
this.loading = false;
this.messageService.error('删除失败');
},
complete: () => this.loading = false,
})
}
showEdit(data: Link) {
this.modalVisible = true;
this.modalTitle = '编辑友链信息';
this.formGroup.patchValue(data);
this.formGroup.controls.oper.setValue('edit')
}
modalConfirm() {
this.modalVisible = false;
const linkReq: Link = new Link();
linkReq.name = this.formGroup.value.name;
linkReq.url = this.formGroup.value.url;
linkReq.open = this.formGroup.value.open;
const oper = this.formGroup.value.oper;
let observable: Observable<Response<Link>>;
if (oper === 'edit') {
linkReq.id = this.formGroup.value.id;
observable = this.apiService.updateLink(linkReq);
} else if (oper === 'add') {
observable = this.apiService.createLink(linkReq);
}
observable.subscribe({
next: data => this.messageService.success('操作成功'),
error: err => this.messageService.success('操作失败,', err.msg),
complete: () => this.getLinks()
})
}
addLink() {
this.modalVisible = true;
this.modalTitle = '新增友链信息';
this.formGroup.controls.oper.setValue('add')
}
}

View File

@@ -0,0 +1,37 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {AdminLinkComponent} from './admin-link.component';
import {
NzButtonModule,
NzCardModule,
NzDividerModule,
NzFormModule, NzInputModule,
NzModalModule,
NzPopconfirmModule, NzSelectModule,
NzTableModule
} from 'ng-zorro-antd';
import {ReactiveFormsModule} from '@angular/forms';
@NgModule({
declarations: [
AdminLinkComponent
],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: AdminLinkComponent}]),
NzCardModule,
NzTableModule,
NzDividerModule,
NzPopconfirmModule,
NzModalModule,
NzFormModule,
ReactiveFormsModule,
NzInputModule,
NzSelectModule,
NzButtonModule
]
})
export class AdminLinkModule {
}

View File

@@ -0,0 +1,65 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {AdminComponent} from './admin.component';
import {AuthGuard} from './auth.guard';
const routes: Routes = [
{
path: '',
component: AdminComponent,
// canActivate: [AuthGuard],
children: [
{
path: 'article',
loadChildren: () => import('./admin-article/admin-article.module').then(mod => mod.AdminArticleModule),
canActivate: [AuthGuard]
},
{
path: 'comment',
loadChildren: () => import('./admin-comment/admin-comment.module').then(mod => mod.AdminCommentModule),
canActivate: [AuthGuard]
},
{
path: 'link',
loadChildren: () => import('./admin-link/admin-link.module').then(mod => mod.AdminLinkModule),
canActivate: [AuthGuard]
},
{
path: 'tag',
loadChildren: () => import('./admin-tag/admin-tag.module').then(mod => mod.AdminTagModule),
canActivate: [AuthGuard]
},
{
path: 'update',
loadChildren: () => import('./admin-update/admin-update.module').then(mod => mod.AdminUpdateModule),
canActivate: [AuthGuard]
},
{
path: 'user',
loadChildren: () => import('./admin-user/admin-user.module').then(mod => mod.AdminUserModule),
canActivate: [AuthGuard]
},
{
path: 'visitor',
loadChildren: () => import('./admin-visitor/admin-visitor.module').then(mod => mod.AdminVisitorModule),
canActivate: [AuthGuard]
},
{
path: '**',
loadChildren: () => import('./admin-dashboard/admin-dashboard.module').then(mod => mod.AdminDashboardModule),
canActivate: [AuthGuard]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class AdminRoutingModule {
}

View File

@@ -0,0 +1,92 @@
<div class="inner-content">
<nz-card nzTitle="" nzSize="small">
<nz-tabset>
<nz-tab nzTitle="分类管理" (nzClick)="editInfo.editFocus=false">
<div style="margin-bottom: 15px;">
<nz-input-group *ngIf="editInfo.editFocus&&editInfo.isAdd" [nzPrefix]="tagIcon"
style="width: 200px">
<input type="text" nz-input [(ngModel)]="editInfo.name" (keyup.enter)="addCategory()"
[autofocus]="editInfo.editFocus"
(blur)="editInfo.editFocus=false">
</nz-input-group>
<button nz-button (click)="addCategory()">新增分类</button>
<button nz-button (click)="editInfo.editFocus=false" *ngIf="editInfo.editFocus&&editInfo.isAdd">取消
</button>
</div>
<nz-table #table [nzData]="categoryList" [nzTotal]="categoryList.length"
(nzPageIndexChange)="getCategory()"
nzFrontPagination="false" [nzLoading]="loading">
<thead>
<th>分类名</th>
<th>分类文章数量</th>
<th>操作</th>
</thead>
<tbody>
<tr *ngFor="let data of table.data">
<td>
<span *ngIf="!editInfo.editFocus||data.id!==editInfo.id">{{data.name}}</span>
<nz-input-group *ngIf="!editInfo.isAdd&&editInfo.editFocus&&data.id===editInfo.id"
[nzPrefix]="tagIcon" style="width: 300px">
<input type="text" nz-input [(ngModel)]="editInfo.name" nzSize="small"
[autofocus]="editInfo.editFocus&&data.id===editInfo.id"
(keyup.enter)="edit('category')" (blur)="editInfo.editFocus=false">
<button nz-button (click)="edit('category')" nzSize="small">更新</button>
<button nz-button (click)="editInfo.editFocus=false" nzSize="small">取消</button>
</nz-input-group>
</td>
<td>
<nz-tag [nzColor]="'purple'">{{data.articles ? data.articles.length : 0}}</nz-tag>
</td>
<td>
<a (click)="editFocus(data)" class="edit-opr">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a [routerLink]="'/categories/'+data.name" class="show-opr">查看</a>
<nz-divider nzType="vertical"></nz-divider>
<a nz-popconfirm nzPopconfirmTitle="确定要删除这个分类吗?" nzOkText="删除" nzCancelText="点错了"
(nzOnConfirm)="delete(data.id,'category')" class="del-opr">删除</a>
</td>
</tr>
</tbody>
</nz-table>
</nz-tab>
<nz-tab nzTitle="标签管理" (nzClick)="editInfo.editFocus=false">
<nz-table #tagTable [nzData]="tagPageList.list" [nzTotal]="tagPageList.total"
[(nzPageIndex)]="pageIndex"
[nzPageSize]="pageSize" (nzPageIndexChange)="getTag()" nzFrontPagination="false">
<thead>
<th>标签名</th>
<th>分类文章数量</th>
<th>操作</th>
</thead>
<tbody>
<tr *ngFor="let data of tagTable.data">
<td>
<span *ngIf="!editInfo.editFocus||data.id!==editInfo.id">{{data.name}}</span>
<nz-input-group *ngIf="!editInfo.isAdd&&editInfo.editFocus&&data.id===editInfo.id"
[nzPrefix]="tagIcon" style="width: 300px">
<input type="text" nz-input [(ngModel)]="editInfo.name" nzSize="small"
[autofocus]="editInfo.editFocus&&data.id===editInfo.id"
(keyup.enter)="edit('tag')" (blur)="editInfo.editFocus=false">
<button nz-button (click)="edit('tag')" nzSize="small">更新</button>
<button nz-button (click)="editInfo.editFocus=false" nzSize="small">取消</button>
</nz-input-group>
</td>
<td>
<nz-tag [nzColor]="'purple'">{{data.articles ? data.articles.length : 0}}</nz-tag>
</td>
<td>
<a (click)="editFocus(data)" class="edit-opr">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a [routerLink]="'/tags/'+data.name" class="show-opr">查看</a>
<nz-divider nzType="vertical"></nz-divider>
<a nz-popconfirm nzPopconfirmTitle="确定要删除这个标签吗?" nzOkText="删除" nzCancelText="点错了"
(nzOnConfirm)="delete(data.id,'tag')" class="del-opr">删除</a>
</td>
</tr>
</tbody>
</nz-table>
</nz-tab>
</nz-tabset>
</nz-card>
</div>
<ng-template #tagIcon><i nz-icon nzType="tag" nzTheme="outline"></i></ng-template>

View File

@@ -0,0 +1,141 @@
import {Component, OnInit} from '@angular/core';
import {NzMessageService} from 'ng-zorro-antd';
import {Category, Tag} from '../../../class/Tag';
import {ApiService} from '../../../api/api.service';
import {PageList} from '../../../class/HttpReqAndResp';
import {Title} from '@angular/platform-browser';
@Component({
selector: 'app-admin-tag',
templateUrl: './admin-tag.component.html',
styleUrls: ['./admin-tag.component.less']
})
export class AdminTagComponent implements OnInit {
constructor(private apiService: ApiService, private nzMessageService: NzMessageService, private title: Title) {
}
loading: boolean = true;
categoryList: Category[] = [];
editInfo = {
id: null,
name: null,
editFocus: false,
isAdd: false
}
tagPageList: PageList<Tag> = new PageList<Tag>();
pageIndex: number = 1;
pageSize: number = 10;
ngOnInit(): void {
this.title.setTitle('小海博客 | 标签分类管理')
this.getCategory();
this.getTag();
}
getCategory = () => this.apiService.categories().subscribe({
next: data => this.categoryList = data.result,
complete: () => this.loading = false,
error: err => this.loading = false
})
getTag = () => this.apiService.tags(this.pageIndex, this.pageSize).subscribe({
next: data => this.tagPageList = data.result,
complete: () => this.loading = false,
error: err => this.loading = false
})
delete(id, mode: 'tag' | 'category') {
this.loading = true;
if (mode === 'tag') {
this.apiService.deleteTag(id).subscribe({
next: data => {
this.nzMessageService.success('删除成功')
this.getTag();
},
complete: () => this.loading = false,
error: err => {
this.nzMessageService.error(err.msg)
this.loading = false
}
})
} else if (mode === 'category') {
this.apiService.deleteCategory(id).subscribe({
next: data => {
this.nzMessageService.success('删除成功')
this.getCategory();
},
complete: () => this.loading = false,
error: err => {
this.nzMessageService.error(err.msg)
this.loading = false
}
})
}
}
editFocus(data: Category) {
this.editInfo.isAdd = false;
this.editInfo.id = data.id;
this.editInfo.name = data.name;
this.editInfo.editFocus = true;
}
edit(mode: 'tag' | 'category') {
this.loading = true;
if (mode === 'tag') {
this.apiService.updateTag(this.editInfo.id, this.editInfo.name).subscribe({
next: data => {
this.nzMessageService.success('更新成功')
this.getTag();
},
complete: () => this.loading = false,
error: err => {
this.nzMessageService.error(err.msg)
this.loading = false
}
})
} else if (mode === 'category') {
this.apiService.updateCategory(this.editInfo.id, this.editInfo.name).subscribe({
next: data => {
this.nzMessageService.success('更新成功')
this.getCategory();
},
complete: () => this.loading = false,
error: err => {
this.nzMessageService.error(err.msg)
this.loading = false
}
})
}
this.editInfo.editFocus = false
this.editInfo.name = null
}
addCategory() {
this.editInfo.isAdd = true
if (!this.editInfo.editFocus && this.editInfo.isAdd) {
this.editInfo.name = null;
this.editInfo.id = null;
this.editInfo.editFocus = true;
return
}
this.apiService.createCategory(this.editInfo.name).subscribe({
next: data => {
this.nzMessageService.success('新增成功')
this.getCategory();
},
complete: () => this.loading = false,
error: err => {
this.nzMessageService.error(err.msg)
this.loading = false
}
});
this.editInfo.editFocus = false
this.editInfo.isAdd = false
this.editInfo.name = null
}
}

View File

@@ -0,0 +1,35 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {AdminTagComponent} from './admin-tag.component';
import {
NzButtonModule,
NzCardModule,
NzDividerModule, NzIconModule,
NzInputModule, NzPopconfirmModule,
NzTableModule, NzTabsModule, NzTagModule,
} from 'ng-zorro-antd';
import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [
AdminTagComponent
],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: AdminTagComponent}]),
NzCardModule,
NzTableModule,
NzDividerModule,
NzInputModule,
FormsModule,
NzTabsModule,
NzPopconfirmModule,
NzButtonModule,
NzIconModule,
NzTagModule,
]
})
export class AdminTagModule {
}

View File

@@ -0,0 +1,32 @@
<div class="inner-content">
<nz-card nzTitle="更新内容管理" nzSize="small">
<button nz-button (click)="showModal()" style="margin-bottom: 15px;">新增</button>
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
[nzPageSize]="pageSize" [nzLoading]="loading"
(nzPageIndexChange)="getUpdateInfo()" nzFrontPagination="false">
<thead>
<th>更新内容</th>
<th>更新日期</th>
<th>操作</th>
</thead>
<tbody>
<tr *ngFor="let data of table.data">
<td nz-typography nzEllipsis="true" [nzTooltipTitle]="data.info" nzTooltipPlacement="top"
nz-tooltip>{{data.info}}</td>
<td>{{data.time}}</td>
<td>
<a (click)="showModal(data)" class="edit-opr">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a nz-popconfirm nzPopconfirmTitle="确定要删除这个更新吗?" nzOkText="删除" nzCancelText="点错了"
(nzOnConfirm)="deleteUpdateInfo(data.id)" class="del-opr">删除</a>
</td>
</tr>
</tbody>
</nz-table>
</nz-card>
</div>
<nz-modal [(nzVisible)]="modalData.visible" [nzClosable]="true" (nzOnCancel)="modalData.visible = false"
(nzOnOk)="confirm()" [nzTitle]="modalData.title">
<textarea nz-input [(ngModel)]="modalData.content" [nzAutosize]="{ minRows: 2, maxRows: 8 }"></textarea>
</nz-modal>

View File

@@ -0,0 +1,3 @@
td {
max-width: 300px;
}

View File

@@ -0,0 +1,95 @@
import {Component, OnInit} from '@angular/core';
import {NzMessageService} from 'ng-zorro-antd';
import {Title} from '@angular/platform-browser';
import {Observable} from 'rxjs';
import {ApiService} from '../../../api/api.service';
import {PageList, Response} from '../../../class/HttpReqAndResp';
import {UpdateInfo} from '../../../class/UpdateInfo';
@Component({
selector: 'app-admin-update',
templateUrl: './admin-update.component.html',
styleUrls: ['./admin-update.component.less']
})
export class AdminUpdateComponent implements OnInit {
constructor(private apiService: ApiService, private nzMessage: NzMessageService, private title: Title) {
}
pageIndex: number = 1;
pageSize: number = 10;
pageList: PageList<UpdateInfo> = new PageList();
loading: boolean = true;
modalData = {
visible: false,
content: null,
id: null,
title: null
};
ngOnInit(): void {
this.title.setTitle('小海博客 | 更新信息管理')
this.getUpdateInfo();
}
getUpdateInfo = () => this.apiService.webUpdatePage(this.pageSize, this.pageIndex).subscribe({
next: data => this.pageList = data.result,
complete: () => this.loading = false,
error: err => this.loading = false
})
deleteUpdateInfo(id) {
this.loading = true;
this.apiService.deleteWebUpdateInfo(id).subscribe({
next: data => {
this.nzMessage.success('删除成功')
this.loading = false;
this.getUpdateInfo();
},
error: err => {
this.nzMessage.error(err.msg)
this.loading = false
}
})
}
confirm() {
this.loading = true;
this.modalData.visible = false;
let observable: Observable<Response<UpdateInfo>>
if (this.modalData.id) {
observable = this.apiService.updateWebUpdateInfo(this.modalData.id, this.modalData.content)
} else {
observable = this.apiService.createWebUpdateInfo(this.modalData.content)
}
observable.subscribe({
next: data => {
this.nzMessage.success('操作成功')
this.loading = false;
this.getUpdateInfo();
},
error: err => {
this.nzMessage.error(err.msg)
this.loading = false
}
})
console.log(this.modalData);
}
showModal(data?: UpdateInfo) {
this.modalData.id = null;
this.modalData.title = '新增更新信息';
this.modalData.content = null;
if (data) {
this.modalData.id = data.id;
this.modalData.content = data.info;
this.modalData.title = '编辑更新信息';
}
this.modalData.visible = true;
}
}

View File

@@ -0,0 +1,37 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {AdminUpdateComponent} from './admin-update.component';
import {
NzButtonModule,
NzCardModule,
NzDividerModule, NzInputModule, NzModalModule,
NzPopconfirmModule,
NzTableModule,
NzToolTipModule,
NzTypographyModule
} from 'ng-zorro-antd';
import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [
AdminUpdateComponent
],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: AdminUpdateComponent}]),
NzCardModule,
NzTableModule,
NzTypographyModule,
NzToolTipModule,
NzDividerModule,
NzPopconfirmModule,
NzModalModule,
FormsModule,
NzButtonModule,
NzInputModule
]
})
export class AdminUpdateModule {
}

View File

@@ -0,0 +1,117 @@
<div class="inner-content">
<nz-card nzTitle="用户管理" nzSize="small">
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
[nzPageSize]="pageSize" [nzLoading]="loading"
(nzPageIndexChange)="getUser()" nzFrontPagination="false">
<thead>
<th>邮箱</th>
<th>昵称</th>
<th>角色</th>
<th>邮箱验证状态</th>
<th>操作</th>
</thead>
<tbody>
<tr *ngFor="let data of table.data">
<td>{{data.email}}</td>
<td>{{data.displayName}}</td>
<td>
<nz-tag [nzColor]="'blue'" *ngIf="data.role == 'admin'">{{data.role}}</nz-tag>
<nz-tag [nzColor]="'purple'" *ngIf="data.role == 'user'">{{data.role}}</nz-tag>
</td>
<td>
<nz-tag [nzColor]="'green'" *ngIf="data.emailStatus">已验证</nz-tag>
<nz-tag [nzColor]="'red'" *ngIf="!data.emailStatus">未验证</nz-tag>
</td>
<td>
<a (click)="showModal(true, data)" class="edit-opr">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a (click)="showModal(false, data)" class="show-opr">查看</a>
<nz-divider nzType="vertical"></nz-divider>
<a nz-popconfirm nzPopconfirmTitle="确定要删除这个用户吗?" nzOkText="删除" nzCancelText="点错了"
(nzOnConfirm)="deleteUser(data.id)" class="del-opr">删除</a>
</td>
</tr>
</tbody>
</nz-table>
</nz-card>
</div>
<nz-modal [(nzVisible)]="modalData.visible" [nzClosable]="true" [nzTitle]="modalData.title"
(nzOnCancel)="modalData.visible = false" (nzOnOk)="modalConfirm()"
[nzFooter]="modalData.isEdit?editContentFooter:showContentFooter"
[nzContent]="showContent">
<ng-template #showContent>
<form nz-form [formGroup]="formGroup" (ngSubmit)="modalConfirm()">
<nz-form-item>
<nz-form-label nzSpan="4" nzRequired>邮箱</nz-form-label>
<nz-form-control nzSpan="18">
<input type="email" nz-input formControlName="email"
[disabled]="!modalData.isEdit">
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzSpan="4" nzRequired>昵称</nz-form-label>
<nz-form-control nzSpan="18">
<input type="text" nz-input formControlName="displayName" [disabled]="!modalData.isEdit">
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzSpan="4" nzRequired>角色</nz-form-label>
<nz-form-control nzSpan="18">
<nz-select formControlName="role" [nzDisabled]="!modalData.isEdit||formGroup.value.id==user.id">
<nz-option nzValue="admin" nzLabel="admin"></nz-option>
<nz-option nzValue="user" nzLabel="user"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzSpan="4" nzRequired>状态</nz-form-label>
<nz-form-control nzSpan="18">
<nz-radio-group formControlName="emailStatus" [nzDisabled]="!modalData.isEdit">
<label nz-radio [nzValue]="true">邮箱已验证</label>
<label nz-radio [nzValue]="false">邮箱未验证</label>
</nz-radio-group>
</nz-form-control>
</nz-form-item>
<nz-form-item *ngIf="modalData.isEdit">
<nz-form-label nzSpan="4">密码</nz-form-label>
<nz-form-control nzSpan="18">
<a *ngIf="!modalData.resetPwd" (click)="modalData.resetPwd = true">
重设密码<i nz-icon nzType="edit" nzTheme="twotone" style="margin-left: 10px;"></i>
</a>
<nz-input-group *ngIf="modalData.resetPwd" [nzSuffix]="cancelBtn" nzSize="small">
<input type="password" nz-input formControlName="pwd" autocomplete="new-password"
[disabled]="!modalData.isEdit">
<ng-template #cancelBtn>
<button nz-button (click)="modalData.resetPwd = false" nzSize="small" nzType="link">取消
</button>
</ng-template>
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzSpan="4">描述</nz-form-label>
<nz-form-control nzSpan="18">
<textarea nz-input [nzAutosize]="{ minRows: 2, maxRows: 4 }" formControlName="desc"
[disabled]="!modalData.isEdit"></textarea>
</nz-form-control>
</nz-form-item>
</form>
</ng-template>
<ng-template #showContentFooter>
<button nz-button (click)="modalData.visible = false">关闭</button>
</ng-template>
<ng-template #editContentFooter>
<button nz-button (click)="modalData.visible = false">取消</button>
<button nz-button (click)="modalConfirm()" nzType="primary">提交</button>
</ng-template>
</nz-modal>

View File

@@ -0,0 +1,93 @@
import {Component, OnInit} from '@angular/core';
import {NzMessageService} from 'ng-zorro-antd';
import {Title} from '@angular/platform-browser';
import {FormControl, FormGroup} from '@angular/forms';
import {PageList} from '../../../class/HttpReqAndResp';
import {ApiService} from '../../../api/api.service';
import {User} from '../../../class/User';
import {GlobalUserService} from '../../../services/global-user.service';
@Component({
selector: 'app-admin-user',
templateUrl: './admin-user.component.html',
styleUrls: ['./admin-user.component.less']
})
export class AdminUserComponent implements OnInit {
constructor(private apiService: ApiService, private title: Title, private messageService: NzMessageService,
private userService: GlobalUserService) {
this.formGroup = new FormGroup({
id: new FormControl(null),
email: new FormControl(''),
displayName: new FormControl(''),
emailStatus: new FormControl(null),
desc: new FormControl(null),
role: new FormControl(null),
pwd: new FormControl(''),
});
this.userService.watchUserInfo({
next: data => this.user = data.result,
error: null,
complete: null
})
}
pageIndex: number = 1;
pageSize: number = 10;
pageList: PageList<User> = new PageList<User>();
user: User;
loading: boolean = true;
modalData = {
visible: false,
title: null,
isEdit: false,
resetPwd: false
}
formGroup: FormGroup;
ngOnInit(): void {
this.title.setTitle('小海博客 | 用户管理')
this.getUser();
}
getUser = () => this.apiService.adminUsers(this.pageSize, this.pageIndex).subscribe({
next: data => this.pageList = data.result,
complete: () => this.loading = false,
error: err => this.loading = false
})
deleteUser(id) {
this.loading = true;
this.apiService.deleteUser(id).subscribe({
next: data => {
this.messageService.success('删除成功')
this.loading = false;
this.getUser();
},
error: err => {
this.messageService.error(err.msg)
this.loading = false
}
})
}
showModal(isEdit: boolean, data: User) {
this.modalData.visible = true;
this.modalData.isEdit = isEdit;
this.modalData.title = isEdit ? '编辑用户' : '查看用户'
this.formGroup.reset();
this.formGroup.patchValue(data);
}
modalConfirm() {
this.modalData.visible = false
this.apiService.adminUpdateUser(this.formGroup.value).subscribe({
next: data => {
this.getUser();
this.messageService.success('修改用户信息成功');
this.userService.refreshUserInfo();
}
})
}
}

View File

@@ -0,0 +1,41 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {AdminUserComponent} from './admin-user.component';
import {
NzButtonModule,
NzCardModule,
NzDividerModule, NzFormModule, NzIconModule, NzInputModule,
NzModalModule,
NzPopconfirmModule, NzRadioModule, NzSelectModule,
NzTableModule,
NzTagModule
} from 'ng-zorro-antd';
import {ReactiveFormsModule} from '@angular/forms';
@NgModule({
declarations: [
AdminUserComponent
],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: AdminUserComponent}]),
NzCardModule,
NzTableModule,
NzPopconfirmModule,
NzDividerModule,
NzTagModule,
NzModalModule,
NzButtonModule,
NzFormModule,
ReactiveFormsModule,
NzInputModule,
NzSelectModule,
NzRadioModule,
NzIconModule
]
})
export class AdminUserModule {
}

View File

@@ -0,0 +1,24 @@
<div class="inner-content">
<nz-card nzTitle="访客信息管理" nzSize="small">
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
[nzPageSize]="pageSize" [nzLoading]="loading"
(nzPageIndexChange)="getVisitors()" nzFrontPagination="false">
<thead>
<th>ip地址</th>
<th>访问日期</th>
<th>浏览器类型</th>
<th>浏览器版本</th>
<th>系统</th>
</thead>
<tbody>
<tr *ngFor="let data of table.data">
<td>{{data.ip}}</td>
<td>{{data.date}}</td>
<td>{{data.browserName}}</td>
<td>{{data.browserVersion}}</td>
<td>{{data.osname}}</td>
</tr>
</tbody>
</nz-table>
</nz-card>
</div>

View File

@@ -0,0 +1,34 @@
import {Component, OnInit} from '@angular/core';
import {Title} from '@angular/platform-browser';
import {ApiService} from '../../../api/api.service';
import {PageList} from '../../../class/HttpReqAndResp';
import {Visitor} from '../../../class/Visitor';
@Component({
selector: 'app-admin-visitor',
templateUrl: './admin-visitor.component.html',
styleUrls: ['./admin-visitor.component.less']
})
export class AdminVisitorComponent implements OnInit {
constructor(private apiService: ApiService, private title: Title) {
}
pageIndex: number = 1;
pageSize: number = 10;
pageList: PageList<Visitor> = new PageList<Visitor>();
loading: boolean = true;
ngOnInit(): void {
this.title.setTitle('小海博客 | 访客信息管理')
this.getVisitors();
}
getVisitors = () => this.apiService.adminVisitors(false, this.pageSize, this.pageIndex).subscribe({
next: data => this.pageList = data.result,
complete: () => this.loading = false,
error: err => this.loading = false
})
}

View File

@@ -0,0 +1,22 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {AdminVisitorComponent} from './admin-visitor.component';
import {NzButtonModule, NzCardModule, NzDividerModule, NzTableModule} from 'ng-zorro-antd';
@NgModule({
declarations: [
AdminVisitorComponent
],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: AdminVisitorComponent}]),
NzCardModule,
NzTableModule,
NzButtonModule,
NzDividerModule
]
})
export class AdminVisitorModule {
}

View File

@@ -0,0 +1,159 @@
<c-admin-header (infoClicked)="showInfoDrawer()"></c-admin-header>
<nz-layout class="layout">
<nz-sider nzCollapsible
[(nzCollapsed)]="isCollapsed"
[nzBreakpoint]="'lg'"
[nzCollapsedWidth]="0"
[nzZeroTrigger]="zeroTrigger"
nzTheme="light"
*ngIf="user">
<ul nz-menu nzTheme="light" nzMode="inline" [nzInlineCollapsed]="isCollapsed">
<li nz-menu-item routerLink="/admin">
<i nz-icon nzType="dashboard" nzTheme="outline"></i>
<span>后台首页</span>
</li>
<li nz-submenu nzTitle="文章管理" nzIcon="file" *ngIf="user.role=='admin'">
<ul>
<li nz-menu-item>
<a routerLink="/write">
<i nz-icon nzType="form" nzTheme="outline"></i>
<span>新增文章</span>
</a>
</li>
<li nz-menu-item routerLink="/admin/article">
<i nz-icon nzType="ordered-list" nzTheme="outline"></i>
<span>文章列表</span>
</li>
</ul>
</li>
<li nz-menu-item routerLink="/admin/comment">
<i nz-icon nzType="message" nzTheme="outline"></i>
<span>评论管理</span>
</li>
<li nz-menu-item routerLink="/admin/tag" *ngIf="user.role=='admin'">
<i nz-icon nzType="tags" nzTheme="outline"></i>
<span>标签管理</span>
</li>
<!-- <li nz-menu-item routerLink="/admin/category" *ngIf="user.role=='admin'">-->
<!-- <i nz-icon nzType="appstore" nzTheme="outline"></i>-->
<!-- <span>分类管理</span>-->
<!-- </li>-->
<li nz-menu-item routerLink="/admin/user" *ngIf="user.role=='admin'">
<i nz-icon nzType="user" nzTheme="outline"></i>
<span>用户管理</span>
</li>
<li nz-menu-item routerLink="/admin/link" *ngIf="user.role=='admin'">
<i nz-icon nzType="link" nzTheme="outline"></i>
<span>友链管理</span>
</li>
<li nz-menu-item routerLink="/admin/visitor" *ngIf="user.role=='admin'">
<i nz-icon nzType="chrome" nzTheme="outline"></i>
<span>访问管理</span>
</li>
<li nz-menu-item routerLink="/admin/update" *ngIf="user.role=='admin'">
<i nz-icon nzType="arrow-up" nzTheme="outline"></i>
<span>更新管理</span>
</li>
<li nz-menu-item (click)="infoDrawerVisible = true">
<i nz-icon nzType="idcard" nzTheme="outline"></i>
<span>查看信息</span>
</li>
<!--TODO : do something here ..... -->
<nz-card class="myCard" *ngIf="!isCollapsed&&user.role=='admin'">
<p>别管别人言语</p>
<p>做最好的自己</p>
</nz-card>
<nz-card class="myCard" *ngIf="!isCollapsed&&user.role=='user'">
<p>欢迎来访小海博客</p>
</nz-card>
</ul>
</nz-sider>
<nz-layout>
<nz-content>
<div class="inner-content">
<router-outlet></router-outlet>
</div>
</nz-content>
<nz-footer>© <a href="https://www.celess.cn">小海博客</a> - Design by 小海</nz-footer>
</nz-layout>
</nz-layout>
<nz-drawer [nzClosable]="false" [nzVisible]="infoDrawerVisible" nzPlacement="right"
[nzTitle]="sayHelloTemp" (nzOnClose)="infoDrawerVisible = false">
<p>您最近一次登录是在:</p>
<p>{{user.recentlyLandedDate}}</p>
<br><br>
<nz-descriptions [nzColumn]="1">
<nz-descriptions-item nzTitle="邮箱">
<!-- 抽屉的宽度是256px -->
<div style="width: 256px">
<i nz-icon nzType="mail" class="icon" nzTheme="twotone"></i>
<span>{{user.email}}</span>
<i nz-icon nzType="edit" class="edit-icon" nzTheme="twotone" (click)="showEditInfoModal()"></i>
</div>
</nz-descriptions-item>
<nz-descriptions-item nzTitle="昵称">
<div style="width: 256px">
<i nz-icon nzType="crown" class="icon" nzTheme="twotone"></i>
<span>{{user.displayName}}</span>
<i nz-icon nzType="edit" class="edit-icon" nzTheme="twotone" (click)="showEditInfoModal()"></i>
</div>
</nz-descriptions-item>
<nz-descriptions-item nzTitle="描述" *ngIf="user.desc">
<div style="width: 256px">
<i nz-icon nzType="info-circle" class="icon" nzTheme="twotone"></i>
<span>{{user.desc}}</span>
<i nz-icon nzType="edit" class="edit-icon" nzTheme="twotone" (click)="showEditInfoModal()"></i>
</div>
</nz-descriptions-item>
</nz-descriptions>
<div nz-row style="text-align: center">
<a (click)="logout()" style="width: 100%">注销</a>
</div>
<!-- TODO: 展示更多信息 -->
</nz-drawer>
<nz-modal [(nzVisible)]="editInfoModalVisible" (nzOnOk)="modalConfirm()" (nzOnCancel)="editInfoModalVisible=false"
nzTitle="编辑信息">
<form nz-form [formGroup]="editInfoFormGroup" (ngSubmit)="modalConfirm()">
<nz-form-item>
<nz-form-label>邮箱</nz-form-label>
<nz-form-control>
<input nz-input disabled formControlName="email">
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label>昵称</nz-form-label>
<nz-form-control>
<input nz-input formControlName="displayName" placeholder="默认为你的邮箱地址">
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label>描述</nz-form-label>
<nz-form-control>
<input nz-input formControlName="desc" placeholder="请输入自我介绍">
</nz-form-control>
</nz-form-item>
</form>
</nz-modal>
<ng-template #zeroTrigger>
<i nz-icon nzType="menu-fold" nzTheme=outline></i>
</ng-template>
<ng-template #sayHelloTemp>
<span>{{sayHelloContent}}</span>
<i nz-icon [nzType]="'smile'" [nzTheme]="'twotone'" nzTwotoneColor="#52c41a" style="margin-left: 5px"></i>
</ng-template>

View File

@@ -0,0 +1,48 @@
.myCard {
width: 180px;
margin-left: 10px;
}
nz-layout, nz-sider {
min-height: 100%;
}
.layout {
padding-top: 80px;
}
p {
text-align: center;
}
nz-content {
margin-left: 30px;
margin-right: 30px;
}
.inner-content {
padding: 15px;
background: #fff;
min-height: 560px;
height: 100%;
}
.icon {
margin-right: 5px;
}
.edit-icon{
margin-left: 10px;
cursor: pointer;
}
nz-footer {
text-align: center;
}
@media only screen and (max-width: 768px) {
nz-content {
margin-left: 10px;
margin-right: 10px;
}
}

View File

@@ -0,0 +1,92 @@
import {Component, OnInit} from '@angular/core';
import {FormControl, FormGroup} from '@angular/forms';
import {NzMessageService} from 'ng-zorro-antd';
import {Router} from '@angular/router';
import {GlobalUserService} from '../../services/global-user.service';
import {User} from '../../class/User';
import {ApiService} from '../../api/api.service';
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.less']
})
export class AdminComponent implements OnInit {
constructor(public gUserService: GlobalUserService, private apiService: ApiService, private messageService: NzMessageService,
private router: Router) {
this.gUserService.watchUserInfo({
complete: () => null,
error: (err) => null,
next: data => {
console.log('更新user')
this.user = data.result
if (data.result) this.initHelloWords()
}
}
)
}
user: User;
isCollapsed: boolean = false;
infoDrawerVisible: boolean = false;
sayHelloContent: string;
editInfoModalVisible: boolean = false;
editInfoFormGroup: FormGroup;
showInfoDrawer = () => this.infoDrawerVisible = !this.infoDrawerVisible;
logout() {
this.gUserService.logout();
this.router.navigateByUrl('/')
}
ngOnInit(): void {
this.editInfoFormGroup = new FormGroup({
desc: new FormControl(),
displayName: new FormControl(),
email: new FormControl({value: null, disabled: true})
});
}
private initHelloWords() {
const hours = new Date().getHours();
if (hours < 6) {
this.sayHelloContent = `夜深了,注意早点休息哦!${this.user.displayName}`
} else if (hours < 10) {
this.sayHelloContent = `早上好呀!${this.user.displayName}`
} else if (hours < 14) {
this.sayHelloContent = `中午好呀!${this.user.displayName}`
} else if (hours < 19) {
this.sayHelloContent = `下午好呀!${this.user.displayName}`
} else if (hours < 22) {
this.sayHelloContent = `晚上好呀!${this.user.displayName}`
} else {
this.sayHelloContent = `时间不早了,注意休息哦!${this.user.displayName}`
}
}
showEditInfoModal() {
this.editInfoModalVisible = true;
this.infoDrawerVisible = false;
this.editInfoFormGroup.patchValue(this.user);
}
modalConfirm() {
const desc = this.editInfoFormGroup.value.desc;
const displayName = this.editInfoFormGroup.value.displayName;
this.apiService.updateUserInfo(desc, displayName).subscribe({
next: data => {
this.messageService.success('修改信息成功')
this.gUserService.refreshUserInfo();
},
error: err => {
this.messageService.error(err.msg);
this.gUserService.refreshUserInfo();
},
complete: null
});
this.editInfoModalVisible = false;
}
}

View File

@@ -0,0 +1,25 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AdminRoutingModule} from './admin-routing.module';
import {AdminComponent} from './admin.component';
import {NgZorroAntdModule} from 'ng-zorro-antd';
import {NzSpaceModule} from 'ng-zorro-antd/space';
import {AdminHeaderComponent} from '../../components/admin-header/admin-header.component';
import {ReactiveFormsModule} from '@angular/forms';
@NgModule({
declarations: [
AdminHeaderComponent,
AdminComponent
],
imports: [
CommonModule,
AdminRoutingModule,
NgZorroAntdModule,
NzSpaceModule,
ReactiveFormsModule
]
})
export class AdminModule {
}

View File

@@ -0,0 +1,70 @@
import {Injectable} from '@angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router} from '@angular/router';
import {Observable, Observer} from 'rxjs';
import {User} from '../../class/User';
import {GlobalUserService} from '../../services/global-user.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private userService: GlobalUserService, private router: Router) {
// this.userService.refreshUserInfo();
}
userInfo: User;
visitCount: number = 0; // 记录一共走过几次canActivate
private path: string;
private readonly loginPath: string = '/user/login';
private observable: Observable<boolean>;
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
this.path = state.url.indexOf('?') > 0 ? state.url.substr(0, state.url.indexOf('?')) : state.url;
this.visitCount++;
this.observable = new Observable<boolean>(observer => {
if (!this.userInfo) {
this.watchUserInfo(observer);
} else {
this.checkPath(observer);
}
});
return this.observable;
}
watchUserInfo(observer: Observer<boolean>) {
this.userService.watchUserInfo({
complete: null,
error: (err) => {
// 请求重复
if (err.code !== -1) {
observer.next(false);
this.router.navigateByUrl(this.loginPath);
}
},
next: data => {
this.userInfo = data.result;
this.checkPath(observer);
}
})
}
checkPath(observer: Observer<boolean>) {
switch (this.path) {
case '/admin/article':
case '/admin/link':
case '/admin/tag':
case '/admin/update':
case '/admin/user':
case '/admin/visitor':
if (this.userInfo.role !== 'admin') {
observer.next(false)
if (this.visitCount === 1) this.router.navigateByUrl('/admin')
return;
}
}
observer.next(true);
}
}

View File

@@ -0,0 +1,18 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ArticleComponent} from './article.component';
const routes: Routes = [
{path: '**', component: ArticleComponent}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class ArticleRoutingModule {
}

View File

@@ -0,0 +1,109 @@
<div id="main">
<div id="article-content"></div>
<ng-template [ngIf]="article">
<span id="over">over</span>
<!-- 文章版权 -->
<div id="copyright">
<p>本文作者:{{article.authorName}} </p>
<p>{{article.original ? "本文" : "原文"}}链接:{{article.original ? copyRightUrl : article.url}}</p>
<p>版权声明:转载请注明出处</p>
</div>
<nz-divider></nz-divider>
<div class="article-tag" id="tag">
<!-- TODO -->
<span *ngFor="let item of (article.tags||'')" class="tag">
<i nz-icon nzType="tag" nzTheme="fill"></i>
<a class="tag" [routerLink]="['/tags']" [queryParams]="{name:item}">{{item}}</a>
</span>
</div>
<div class="article-bAnda">
<a (click)="toArticle(article.nextArticleId)" [class.disabled]="article.nextArticleId==-1">
<i nz-icon nzType="left" nzTheme="outline"></i> {{article.nextArticleTitle}}
</a>
<a (click)="toArticle(article.preArticleId)" [class.disabled]="article.preArticleId==-1"
style="float: right" id="pre">
{{article.preArticleTitle}} <i nz-icon nzType="right" nzTheme="outline"></i>
</a>
</div>
<!-- TODO:::评论列表 -->
</ng-template>
<nz-comment id="f-comment">
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="avatarImgUrl"></nz-avatar>
<nz-comment-content>
<nz-form-item>
<textarea [(ngModel)]="comment.content" nz-input rows="4" [disabled]="!user"
[nzAutosize]="{ minRows: 2, maxRows: user?6:2 }"></textarea>
<span *ngIf="!user">请先 <a routerLink="/user/login"
[queryParams]="{url:'/article/'+articleId}">登录</a></span>
</nz-form-item>
<nz-form-item>
<button nz-button nzType="primary" [nzLoading]="!user&&submitting"
[disabled]="!comment.content"
(click)="submitComment(true)">评论
</button>
</nz-form-item>
</nz-comment-content>
</nz-comment>
<div class="tab">评论</div>
<div class="tab-bottom"></div>
<div *ngIf="commentPage&&!commentPage.list.length" class="no-comment-tip">暂无评论,赶快去抢个沙发吧</div>
<ng-template [ngIf]="commentPage">
<nz-list [nzLoading]="!commentPage">
<nz-list-item *ngFor="let comment of commentPage.list">
<nz-comment [nzAuthor]="comment.authorName" [nzDatetime]="comment.date" style="width: 100%">
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="comment.authorAvatarImgUrl"></nz-avatar>
<nz-comment-content>
<p style="font-size: larger">{{ comment.content }}</p>
</nz-comment-content>
<nz-comment-action>
<i nz-icon nzType="message" nzTheme="outline"></i>
<button nz-button nzType="link" (click)="resp(comment.id,comment.authorName)">回复</button>
</nz-comment-action>
<nz-list-item *ngFor="let com of comment.respComment">
<nz-comment [nzAuthor]="com.authorName" [nzDatetime]="com.date">
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="com.authorAvatarImgUrl"></nz-avatar>
<nz-comment-content>
<p style="font-size: larger">{{ com.content }}</p>
</nz-comment-content>
<!--<nz-comment-action>
<i nz-icon nzType="message" nzTheme="outline"></i>
<button nz-button nzType="link" (click)="resp(comment.id,com.authorName)">回复</button>
</nz-comment-action>-->
</nz-comment>
</nz-list-item>
<nz-form-item *ngIf="pid==comment.id">
<nz-input-group [nzAddOnBefore]="respName">
<textarea nz-input [(ngModel)]="content" placeholder="写出你的想法"
[nzAutosize]="{ minRows: 2, maxRows: 6 }" [disabled]="!user">
</textarea>
<ng-template #respName>
<span>@{{name}}</span>
</ng-template>
</nz-input-group>
<div *ngIf="!user">请先 <a routerLink="/user/login"
[queryParams]="{url:'/article/'+articleId}">登录</a></div>
<button nz-button (click)="pid=null" style="margin-top: 10px;">取消</button>
<button nz-button nzType="primary" (click)="submitComment(false)"
style="margin-left: 30px;margin-top: 10px;" [nzLoading]="!user&&submitting">回复
</button>
</nz-form-item>
</nz-comment>
</nz-list-item>
</nz-list>
</ng-template>
</div>

View File

@@ -0,0 +1,113 @@
nz-sider {
width: 200px;
margin-top: 24px;
}
nz-layout {
background: white;
}
nz-content {
margin: 24px 15%;
width: 70%;
background: white;
}
#article-content {
img {
margin: 0 auto;
}
}
#over {
display: block;
width: 80px;
height: 80px;
border-radius: 50%;
line-height: 80px;
margin: 15px auto;
text-align: center;
font-weight: bold;
font-size: 2em;
background: #3bb4f2;
color: white
}
#copyright {
width: 90%;
margin: 15px 5%;
border-left: 5px solid #FF1700;
padding: 10px;
background: #ececec
}
.tag {
color: #666666;
margin-right: 10px;
i {
margin-right: 5px;
}
}
.article-bAnda {
margin-top: 10px;
a {
color: black;
font-size: large;
}
a:hover {
color: #00a8c6;
}
.disabled {
pointer-events: none;
cursor: none;
color: gray;
user-select: none;
}
}
.tab {
border: 1px solid #f50;
border-bottom: 1px solid #ffffff;
padding: 8px 10px;
width: 50px;
user-select: none;
}
.tab-bottom {
border-top: 1px solid #f50;
margin-left: 50px;
}
.no-comment-tip {
line-height: 50px;
text-align: center;
}
#main {
background: white;
padding: 0 15%
}
@media screen and (max-width: 940px) {
#main {
padding: 0 2%;
}
.article-bAnda {
height: 70px;
max-height: 110px;
a {
display: block;
}
}
#f-comment {
margin: 30px 3%
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ArticleComponent } from './article.component';
describe('ArticleComponent', () => {
let component: ArticleComponent;
let fixture: ComponentFixture<ArticleComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ArticleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ArticleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,112 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {ApiService} from '../../api/api.service';
import {Article} from '../../class/Article';
import {Title} from '@angular/platform-browser';
import {User} from '../../class/User';
import {CommentReq} from '../../class/Comment';
import {PageList} from '../../class/HttpReqAndResp';
import {Comment} from '../../class/Comment';
import {GlobalUserService} from '../../services/global-user.service';
declare var editormd;
declare var $;
@Component({
selector: 'view-article',
templateUrl: './article.component.html',
styleUrls: ['./article.component.less']
})
export class ArticleComponent implements OnInit {
constructor(private activatedRoute: ActivatedRoute,
private apiService: ApiService,
private userService: GlobalUserService,
private titleService: Title,
private router: Router) {
this.articleId = +activatedRoute.snapshot.paramMap.get('id');
}
articleId: number;
article: Article;
copyRightUrl: string;
user: User;
submitting: boolean = false;
comment: CommentReq;
// 作为输入框@ 的提示
name: string;
commentPage: PageList<Comment>;
avatarImgUrl: string;
pid: number;
content: string;
ngOnInit() {
this.toArticle(this.articleId);
this.userService.watchUserInfo({
complete: () => null,
error: (err) => null,
next: data => {
this.user = data.result;
if (data.result) this.avatarImgUrl = data.result.avatarImgUrl;
}
});
this.comment = new CommentReq(true);
}
parseMd(md: string) {
editormd.markdownToHTML('article-content', {
markdown: this.article.mdContent,
htmlDecode: 'style,script,iframe', // you can filter tags decode
toc: false,
tocm: false, // Using [TOCM]
// tocContainer: '#article-slider', // 自定义 ToC 容器层
// tocDropdown: true,
emoji: true,
taskList: true,
flowChart: true, // 默认不解析
});
}
toArticle(id: number) {
this.commentPage = null;
this.router.navigateByUrl('/article/' + id);
this.apiService.getArticle(id).subscribe({
next: data => {
this.apiService.comments(id).subscribe(commData => {
this.commentPage = commData.result;
});
document.getElementById('article-content').innerHTML = '';
this.article = data.result;
this.parseMd(data.result.mdContent);
this.titleService.setTitle('小海博客 | ' + data.result.title);
window.scrollTo(0, 0);
}
});
this.copyRightUrl = location.href;
}
// true ==> 一级评论 false ==>二级评论
submitComment(bool: boolean) {
this.submitting = true;
this.comment.articleID = this.articleId;
if (!bool) {
this.comment.content = this.content;
this.comment.pid = this.pid;
}
this.apiService.createComment(this.comment).subscribe(data => {
this.commentPage.list.push(data.result);
this.comment.content = null;
this.comment.pid = null;
this.submitting = false;
}, error => {
this.submitting = false;
});
}
resp(id: number, name: string) {
this.pid = id;
this.name = name;
}
}

View File

@@ -0,0 +1,42 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ArticleRoutingModule} from './article-routing.module';
import {ArticleComponent} from './article.component';
import {
NzAffixModule,
NzAnchorModule, NzAvatarModule,
NzButtonModule,
NzCommentModule,
NzDividerModule, NzFormModule,
NzGridModule,
NzIconModule, NzInputModule,
NzLayoutModule, NzListModule, NzTabsModule
} from 'ng-zorro-antd';
import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [
ArticleComponent
],
imports: [
CommonModule,
ArticleRoutingModule,
NzGridModule,
NzAnchorModule,
NzAffixModule,
NzButtonModule,
NzIconModule,
NzLayoutModule,
NzDividerModule,
NzCommentModule,
NzFormModule,
FormsModule,
NzAvatarModule,
NzInputModule,
NzListModule,
NzTabsModule
]
})
export class ArticleModule {
}

View File

@@ -0,0 +1,18 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {CategoryComponent} from './category.component';
const routes: Routes = [
{path: ':category', component: CategoryComponent},
{path: '**', component: CategoryComponent}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class CategoryRoutingModule {
}

View File

@@ -0,0 +1,28 @@
<div id="main">
<div id="category">
<ul *ngIf="categoryList.length">
<c-tag-tag *ngFor="let category of categoryList"
[tag]="{name:category.name,size:category.articles.length}"
(tagClick)="changeCategory(category)">
</c-tag-tag>
</ul>
<h2 *ngIf="!categoryList.length">暂时没有分类</h2>
</div>
<span id="tip">当前分类为 :
<span style="font-weight: bolder;">{{name}}</span>
</span>
<ul id="detail" *ngIf="articleList&&articleList.list.length">
<c-article-detail-card *ngFor="let article of articleList.list"
[data]="article" [showMediaArea]="false" [showTagArea]="false">
</c-article-detail-card>
</ul>
<div *ngIf="!articleList||!articleList.list.length">
<h2 style="text-align: center">该分类暂无文章</h2>
</div>
</div>

View File

@@ -0,0 +1,29 @@
#main {
width: 75%;
margin: 30px auto;
border-radius: 10px;
padding: 10px;
background: #ffffff;
#tip {
margin-left: 40px;
font-size: large;
}
#category {
text-align: center;
}
#detail {
margin-top: 20px;
}
}
@media screen and (max-width: 940px) {
#main {
width: 100%;
}
#detail{
margin: 10px 40px 10px 0;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CategoryComponent } from './category.component';
describe('CategoryComponent', () => {
let component: CategoryComponent;
let fixture: ComponentFixture<CategoryComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CategoryComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CategoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,71 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../api/api.service';
import {Tag} from '../../class/Tag';
import {NzMessageService} from 'ng-zorro-antd';
import {PageList} from '../../class/HttpReqAndResp';
import {Article} from '../../class/Article';
import {ActivatedRoute} from '@angular/router';
import {Location} from '@angular/common';
import {Title} from '@angular/platform-browser';
@Component({
selector: 'view-category',
templateUrl: './category.component.html',
styleUrls: ['./category.component.less']
})
export class CategoryComponent implements OnInit {
constructor(private apiService: ApiService,
private nzMessageService: NzMessageService,
private activatedRoute: ActivatedRoute,
private location: Location,
private title: Title) {
}
categoryList: Tag[] = [];
private category: Tag;
articleList: PageList<Article>;
name: string;
ngOnInit() {
this.name = this.activatedRoute.snapshot.paramMap.get('category');
this.getCategories(this.name == null);
if (this.name != null) {
this.getArticles(this.name);
}
}
getCategories(needGetArticle: boolean) {
this.apiService.categories().subscribe(data => {
this.categoryList = data.result;
this.category = data.result[0];
if (needGetArticle) {
this.getArticles(this.category.name);
this.name = this.category.name;
this.title.setTitle('小海博客 | 分类 | ' + this.name);
}
}, error => {
this.nzMessageService.error('出现了错误,原因:' + error.msg);
});
}
getArticles(categoryName: string) {
this.apiService.articlesByCategory(categoryName).subscribe(data => {
this.articleList = data.result;
}, error => {
this.nzMessageService.error('出现了错误,原因:' + error.msg);
});
}
changeCategory(category: Tag) {
if (this.name === category.name) {
return;
}
this.category = category;
this.name = category.name;
this.location.replaceState('categories/' + this.name);
this.getArticles(this.name);
this.title.setTitle('小海博客 | 分类 | ' + this.name);
}
}

View File

@@ -0,0 +1,20 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {CategoryComponent} from './category.component';
import {CategoryRoutingModule} from './category-routing.module';
import {NzIconModule, NzToolTipModule} from 'ng-zorro-antd';
import {IndexModule} from '../index/index.module';
@NgModule({
declarations: [CategoryComponent],
imports: [
CommonModule,
CategoryRoutingModule,
NzToolTipModule,
NzIconModule,
IndexModule
]
})
export class CategoryModule {
}

View File

@@ -0,0 +1,7 @@
<div>
<div id="main">
<nz-alert [nzType]="type" [nzMessage]="message"
[nzDescription]="desc" nzShowIcon>
</nz-alert>
</div>
</div>

View File

@@ -0,0 +1,6 @@
#main{
width: 60%;
height: 50px;
margin: 0 auto;
padding: 15% 0;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EmailVerifyComponent } from './email-verify.component';
describe('EmailVerifyComponent', () => {
let component: EmailVerifyComponent;
let fixture: ComponentFixture<EmailVerifyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EmailVerifyComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EmailVerifyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,49 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {ApiService} from '../../api/api.service';
import {Title} from '@angular/platform-browser';
@Component({
selector: 'view-email-verify',
templateUrl: './email-verify.component.html',
styleUrls: ['./email-verify.component.less']
})
export class EmailVerifyComponent implements OnInit {
constructor(private titleService: Title,
private router: Router,
public routerinfo: ActivatedRoute,
private apiService: ApiService) {
titleService.setTitle('小海博客 | 邮箱验证');
}
type: string = 'info';
message: string = '正在验证,请稍等';
desc: string = '';
private email: string;
private verifyId: string;
ngOnInit(): void {
this.email = this.routerinfo.snapshot.queryParams.email;
this.verifyId = this.routerinfo.snapshot.queryParams.verifyId;
if (this.email == null || this.verifyId == null) {
this.type = 'warning';
this.message = '数据不全';
this.desc = '链接可能被修改了,请重新点击邮箱中的链接,或者重新发送邮件';
return;
}
this.apiService.emailVerify(this.verifyId, this.email).subscribe({
next: data => {
this.type = 'success';
this.message = '验证成功';
},
error: e => {
this.type = 'error';
this.message = e.msg;
}
});
}
}

View File

@@ -0,0 +1,24 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {Route, RouterModule} from '@angular/router';
import {EmailVerifyComponent} from './email-verify.component';
import {NzAlertModule} from 'ng-zorro-antd';
const routes: Route[] = [
{path: '**', component: EmailVerifyComponent}
];
@NgModule({
declarations: [
EmailVerifyComponent
],
imports: [
CommonModule,
RouterModule.forChild(routes),
NzAlertModule
]
})
export class EmailVerifyModule {
}

View File

@@ -0,0 +1,35 @@
<nz-card id="article-card" [nzLoading]="data==null">
<h1><a [routerLink]="'/article/'+data.id">{{data.title}}</a></h1>
<div>
<span *ngIf="showMediaArea" id="article-original" [ngClass]="data.original?'original':'reproduced'">
{{data.original ? '原创' : '转载'}}
</span>
<span *ngIf="showMediaArea" class="badge">
<i nz-icon nzType="calendar" nzTheme="outline"></i>
<span>{{data.publishDateFormat}}</span>
</span>
<span *ngIf="showMediaArea" class="badge">
<i nz-icon nzType="user" nzTheme="outline"></i>
<span>{{data.authorName}}</span>
</span>
<span *ngIf="showMediaArea" class="badge">
<i nz-icon nzType="file" nzTheme="outline"></i>
<span>
<a [routerLink]="'/categories/'+data.category">{{data.category}}</a>
</span>
</span>
</div>
<p>{{data.summary}}</p>
<span style="float: right;margin-bottom: 10px">
<a [routerLink]="'/article/'+data.id">阅读更多<i nz-icon nzType="right" nzTheme="outline"></i></a>
</span>
<ng-template [ngIf]="showTagArea&&data.tags.length>0">
<nz-divider></nz-divider>
<div>
<span *ngFor="let tag of data.tags">
<i nz-icon nzType="tag" nzTheme="outline"></i>
<a [routerLink]="'/tags/'+tag">{{tag}}</a>
</span>
</div>
</ng-template>
</nz-card>

View File

@@ -0,0 +1,65 @@
@import "../../../../global-variables";
#article-card {
border-radius: 3px;
margin-bottom: 45px;
h1, p, a {
word-break: break-all;
color: black;
}
h1 {
//border-bottom: 1px solid #ececec;
width: auto;
}
span {
margin-right: 20px;
}
.badge {
i {
padding: 3px 5px;
}
span {
padding: 1px 5px;
}
}
p {
margin-top: 20px;
font-size: 1.1em;
line-height: 2em;
margin-bottom: 0;
}
#article-original {
padding: 3px 6px;
color: white;
border-radius: 5px;
}
.original {
background: #5eb95e;;
}
.reproduced {
background: #f37b1d;;
}
}
/* max-width:910px */
@media screen and (max-width: @max-width) {
span {
margin-right: 0 !important;
padding: 1px 3px !important;
}
i {
padding: 3px !important;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ArticleDetailCardComponent } from './article-detail-card.component';
describe('ArticleDetailCardComponent', () => {
let component: ArticleDetailCardComponent;
let fixture: ComponentFixture<ArticleDetailCardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ArticleDetailCardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ArticleDetailCardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,33 @@
import {Component, Input, OnInit} from '@angular/core';
import {Article} from '../../../../class/Article';
import {ColorList} from '../../../../utils/color';
@Component({
selector: 'c-article-detail-card',
templateUrl: './article-detail-card.component.html',
styleUrls: ['./article-detail-card.component.less']
})
export class ArticleDetailCardComponent implements OnInit {
constructor() {
}
@Input() data: Article;
@Input() showMediaArea: boolean;
@Input() showTagArea: boolean;
ngOnInit() {
if (this.data == null || this.data.id == null) {
throw Error('data 不可为空');
}
if (this.showMediaArea == null) {
// 如果作者名不为空 则显示
this.showMediaArea = this.data.authorName != null;
}
if (this.showTagArea == null) {
this.showTagArea = this.data.tags != null;
}
}
}

View File

@@ -0,0 +1,7 @@
<nz-card [nzTitle]="title?titleTmp:null" style="margin-bottom: 50px;">
<ng-content></ng-content>
</nz-card>
<ng-template #titleTmp>
<div style="text-align: center">{{title}}</div>
</ng-template>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CardDetailComponent } from './card-detail.component';
describe('CardDetailComponent', () => {
let component: CardDetailComponent;
let fixture: ComponentFixture<CardDetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CardDetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CardDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,21 @@
import {Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'c-card-detail',
templateUrl: './card-detail.component.html',
styleUrls: ['./card-detail.component.less']
})
export class CardDetailComponent implements OnInit {
constructor() {
}
@Input() title: string;
// @ContentChildren() c:T;
ngOnInit() {
console.log();
}
}

View File

@@ -0,0 +1,20 @@
<div class="tag-tag" nz-tooltip
style="cursor: pointer"
[nzTooltipTitle]="tag.name"
(click)="click()">
<span class="tag-name"
[style.background-color]="randColor.bgColor"
[style.color]="randColor.fontColor"
[style.font-size]="size=='large'?'large':''"
[style.padding]="size=='large'?'12px 15px':'5px 7px'"
>{{tag.name}}
</span>
<ng-template [ngIf]="enableCount">
<span class="tag-count"
[style.border-color]="randColor.bgColor"
[style.font-size]="size=='large'?'large':''"
[style.padding]="size=='large'?'11px 14px':'4px 6px'"
>{{tag.size}}
</span>
</ng-template>
</div>

View File

@@ -0,0 +1,15 @@
.tag-tag {
display: inline-block;
margin: 0 10px;
line-height: 40px;
user-select: none;
.tag-name {
//padding: 3px 7px;
}
.tag-count {
border: 1px solid white;
//padding: 2px 6px;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TagTagComponent } from './tag-tag.component';
describe('TagTagComponent', () => {
let component: TagTagComponent;
let fixture: ComponentFixture<TagTagComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TagTagComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TagTagComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,37 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {ColorList} from '../../../../utils/color';
import {Router} from '@angular/router';
@Component({
selector: 'c-tag-tag',
templateUrl: './tag-tag.component.html',
styleUrls: ['./tag-tag.component.less']
})
export class TagTagComponent implements OnInit {
constructor(private router: Router) {
}
@Input() tag: { name: string, size: number };
@Input() size: 'default' | 'large' = 'default';
@Input() clickable: boolean; // default true
@Input() enableCount: boolean; // default true
@Output() tagClick = new EventEmitter();
randColor: { bgColor: string, fontColor: string };
ngOnInit() {
const randomNumber = Math.floor(ColorList.length * Math.random());
this.randColor = ColorList[randomNumber];
if (this.clickable == null) {
this.clickable = true;
}
if (this.enableCount == null) {
this.enableCount = true;
}
}
click() {
this.tagClick.emit();
}
}

View File

@@ -0,0 +1,18 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {IndexComponent} from './index.component';
const routes: Routes = [
{path: '**', component: IndexComponent}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class IndexRoutingModule {
}

View File

@@ -0,0 +1,103 @@
<div nz-row id="index-container">
<div nz-col nzSpan="14" nzOffset="2" id="index-left">
<ng-template [ngIf]="articles&&articles.total">
<c-article-detail-card *ngFor="let item of articles.list" [data]="item">
</c-article-detail-card>
</ng-template>
<nz-pagination style="text-align: center"
*ngIf="articles" [nzPageIndex]="articles.pageNum"
[nzTotal]="articles.total"
[nzPageSize]="articles.pageSize"
(nzPageIndexChange)="getArticles($event)">
</nz-pagination>
</div>
<div nz-col nzSpan="5" nzOffset="1" id="index-right">
<!-- 关于博主 -->
<c-card-detail title="关于博主">
<div id="index-bloger-container" title="">
<img id="index-bloger-pic"
[src]="imgUrl" alt="pic">
<div id="index-bloger-desc">
<p><span style="font-weight: bold;font-size: x-large;"
[title]="desc"> 郑 海</span></p>
<p><i nz-icon nzType="blog:location"></i> Location : 武汉</p>
<p><i nz-icon nzType="github" nzTheme="outline"></i>
Github : <a target="_blank" href="https://github.com/xiaohai2271"> link>></a>
</p>
</div>
</div>
<div id="index-bloger-button-area" title="">
<div id="index-bloger-qq-btn">
<i nz-icon nzType="qq" title="QQ二维码" nzTheme="outline"
style="color: #ff8936;border-color: #ff8936;cursor: pointer"
(mouseenter)="changeImg(qqQrImgUrl)" (mouseleave)="changeImg()"></i>
</div>
<div id="index-bloger-wx-btn">
<i nz-icon nzType="wechat" title="微信二维码" nzTheme="outline"
style="color: #7bcfa6;border-color: #7bcfa6;cursor: pointer"
(mouseenter)="changeImg(wxQrImgUrl)" (mouseleave)="changeImg()"></i>
</div>
</div>
</c-card-detail>
<!-- 分类云 -->
<c-card-detail title="分类云">
<div title="" style="text-align: center;user-select: none">
<c-tag-tag *ngFor="let category of categoryList"
[tag]="{name:category.name,size:category.articles.length}"
(tagClick)="toCategory(category.name)">
</c-tag-tag>
</div>
</c-card-detail>
<!-- 标签云 -->
<c-card-detail title="标签云">
<div title="" style="text-align: center;user-select: none">
<c-tag-tag *ngFor="let tag of tagNameAndNumber" [tag]="tag" (tagClick)="toTag(tag.name)">
</c-tag-tag>
</div>
</c-card-detail>
<!-- 网站信息 -->
<c-card-detail title="网站信息" id="index-site-info">
<p *ngIf="counts && counts.articleCount">
<i nz-icon nzType="info-circle" nzTheme="outline"></i>文章总数: {{counts.articleCount}}篇
</p>
<p *ngIf="counts && counts.categoryCount">
<i nz-icon nzType="info-circle" nzTheme="outline"></i>分类总数: {{counts.categoryCount}}个
</p>
<p *ngIf="counts && counts.tagCount">
<i nz-icon nzType="info-circle" nzTheme="outline"></i>标签总数: {{counts.tagCount}}个
</p>
<p *ngIf="counts && counts.visitorCount">
<i nz-icon nzType="info-circle" nzTheme="outline"></i>访客总数: {{counts.visitorCount}}次
</p>
<p *ngIf="lastestUpdate">
<i nz-icon nzType="info-circle" nzTheme="outline"></i>上次更新时间: {{lastestUpdate.lastUpdateTime}}
</p>
<p *ngIf="lastestUpdate&&lastestUpdate.committerDate">
<i nz-icon nzType="info-circle" nzTheme="outline"></i>上次提交代码时间: {{lastestUpdate.committerDate}}
</p>
</c-card-detail>
<c-card-detail class="index-words">
<p>何为遗憾?</p>
<p>鲥鱼多刺 海棠无香 红楼未完。</p>
<p>可否具体?</p>
<p>从别后 忆相逢 几回魂梦与伊同。</p>
<p>可否再具体?</p>
<p>终丢了你。</p>
</c-card-detail>
<c-card-detail class="index-words">
<p>何为放下?</p>
<p>喜你,成疾,药无医。</p>
<p>可否具体?</p>
<p>爱而,不得,终可惜。</p>
<p>可否再具体?</p>
<p>所有,来生,再相依。</p>
</c-card-detail>
</div>
</div>

View File

@@ -0,0 +1,100 @@
@import "../../global-variables";
#index-container {
margin-top: 30px;
#index-bloger-container {
#index-bloger-pic {
width: 120px;
height: 120px;
margin: 0 auto;
}
#index-bloger-desc {
display: inline-block;
height: 120px;
padding-left: 10px;
vertical-align: top;
padding-top: 20px;
word-break: break-all;
}
}
#index-bloger-button-area {
margin-top: 20px;
#index-bloger-qq-btn, #index-bloger-wx-btn {
display: inline-block;
width: 50%;
font-size: large;
text-align: center;
i {
border: 1px solid black;
padding: 10px;
border-radius: 50%;
}
}
}
#index-site-info {
p {
i {
margin-right: 15px;
}
padding-left: 5%;
}
}
.index-tag {
margin: 0 5px;
color: #333333;
width: 100%;
word-break: break-word;
}
.index-words p {
margin-left: 10%;
font-weight: lighter;
font-size: medium;
}
}
@media screen and (max-width: @max-width) {
#index-container {
display: block;
#index-bloger-container {
text-align: center;
width: 100%;
#index-bloger-desc {
width: 100%;
}
}
#index-left {
position: relative;
width: 90%;
margin: 0 5%;
max-width: 100% !important;
}
#index-right {
position: relative;
width: 90%;
margin: 0 5%;
max-width: 100% !important;
}
}
}
// 关于博主 栏小于1420会错位故提前将其设置为居中
@media screen and (max-width: 1420px) {
#index-bloger-container {
text-align: center;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { IndexComponent } from './index.component';
describe('IndexComponent', () => {
let component: IndexComponent;
let fixture: ComponentFixture<IndexComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ IndexComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IndexComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,112 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../api/api.service';
import {Article} from '../../class/Article';
import {NzIconService, NzMessageService} from 'ng-zorro-antd';
import {SvgIconUtil} from '../../utils/svgIconUtil';
import {PageList} from '../../class/HttpReqAndResp';
import {ErrDispatch} from '../../class/ErrDispatch';
import {RequestObj} from '../../class/HttpReqAndResp';
import {Router} from '@angular/router';
import {Tag} from '../../class/Tag';
import {Title} from '@angular/platform-browser';
@Component({
selector: 'view-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.less'],
providers: [ApiService]
})
export class IndexComponent implements OnInit, ErrDispatch {
constructor(private apiService: ApiService,
private iconService: NzIconService,
private nzMessageService: NzMessageService,
private router: Router,
private title: Title) {
this.iconService.addIconLiteral('blog:location', SvgIconUtil.locationIcon);
apiService.setErrDispatch(this);
title.setTitle('小海博客');
}
readonly logoImgUrl: string = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/logo.png';
readonly qqQrImgUrl: string = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/qq.jpg';
readonly wxQrImgUrl: string = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/wx.jpg';
imgUrl: string;
desc: string;
articles: PageList<Article>;
tagNameAndNumber: { name: string, size: number }[];
categoryList: Tag[];
counts: {
articleCount: number,
visitorCount: number,
categoryCount: number,
leaveMsgCount: number,
tagCount: number,
commentCount: number
};
lastestUpdate: {
lastUpdateTime: string;
lastUpdateInfo: string;
lastCommit: string;
committerAuthor: string;
committerDate: string;
commitUrl: string
};
ngOnInit() {
this.imgUrl = this.logoImgUrl;
this.desc = '一个爱好瞎捣鼓的技术宅 :)\n欢迎一起来探讨学习。';
this.getArticles(1);
this.apiService.tagsNac().subscribe({
next: data => this.tagNameAndNumber = data.result,
error: error => {
}
});
this.apiService.counts().subscribe({
next: data => this.counts = data.result,
error: error => {
}
});
this.apiService.lastestUpdate().subscribe({
next: data => this.lastestUpdate = data.result,
error: error => {
}
});
this.apiService.categories().subscribe({
next: data => this.categoryList = data.result,
error: err => {
}
});
this.apiService.visit().subscribe(data => {
});
}
changeImg(url: string = this.logoImgUrl) {
this.imgUrl = url;
}
getArticles(pageNumber: number) {
this.apiService.articles(pageNumber, 10)
.subscribe({
next: data => this.articles = data.result,
error: error => {
}
});
}
errHandler(code: number, msg: string, request?: RequestObj) {
this.nzMessageService.error(msg);
}
toTag(name: string) {
this.router.navigateByUrl('tags/' + encodeURI(name));
}
toCategory(name: string) {
this.router.navigateByUrl('categories/' + encodeURI(name));
}
}

View File

@@ -0,0 +1,44 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {IndexComponent} from './index.component';
import {IndexRoutingModule} from './index-routing.module';
import {ArticleDetailCardComponent} from './components/article-detail-card/article-detail-card.component';
import {
NzBackTopModule,
NzCardModule,
NzDividerModule,
NzGridModule,
NzIconModule,
NzPaginationModule,
NzToolTipModule
} from 'ng-zorro-antd';
import { CardDetailComponent } from './components/card-detail/card-detail.component';
import { TagTagComponent } from './components/tag-tag/tag-tag.component';
@NgModule({
declarations: [
IndexComponent,
ArticleDetailCardComponent,
CardDetailComponent,
TagTagComponent
],
imports: [
CommonModule,
IndexRoutingModule,
NzCardModule,
NzIconModule,
NzDividerModule,
NzGridModule,
NzToolTipModule,
NzPaginationModule,
NzBackTopModule,
],
exports: [
TagTagComponent,
ArticleDetailCardComponent
]
})
export class IndexModule {
}

View File

@@ -0,0 +1,17 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {LeaveMsgComponent} from './leave-msg.component';
const routes: Routes = [
{path: '**', component: LeaveMsgComponent}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class LeaveMsgRoutingModule {
}

View File

@@ -0,0 +1 @@
<p>leave-msg works!</p>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LeaveMsgComponent } from './leave-msg.component';
describe('LeaveMsgComponent', () => {
let component: LeaveMsgComponent;
let fixture: ComponentFixture<LeaveMsgComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LeaveMsgComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LeaveMsgComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'view-leave-msg',
templateUrl: './leave-msg.component.html',
styleUrls: ['./leave-msg.component.less']
})
export class LeaveMsgComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,15 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {LeaveMsgRoutingModule} from './leave-msg-routing.module';
import {LeaveMsgComponent} from './leave-msg.component';
@NgModule({
declarations: [LeaveMsgComponent],
imports: [
CommonModule,
LeaveMsgRoutingModule
]
})
export class LeaveMsgModule {
}

View File

@@ -0,0 +1,18 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {LinkComponent} from './link.component';
const routes: Routes = [
{path: '**', component: LinkComponent}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class LinkRoutingModule {
}

View File

@@ -0,0 +1,52 @@
<div class="site-middle am-animation-slide-top">
<div class="title">
<i nz-icon nzType="smile" nzTheme="outline" class="titleTag"></i><span class="title">友情链接</span>
</div>
<ul class="partner-sites">
<li *ngFor="let link of linkList">
<i nz-icon nzType="link" nzTheme="outline"></i>
<a [href]="link.url" target="_blank" [title]="link.name">{{link.name}}</a>
</li>
<li class="applylink" (click)="showModal=!showModal">申请友链</li>
</ul>
</div>
<div class="placard am-animation-slide-bottom">
<div class="title">
<i nz-icon nzType="smile" nzTheme="outline" class="titleTag"></i><span class="title">友链公告</span>
</div>
<ul class="placard-content">
<li>请确认贵站可正常访问</li>
<li>原创博客、技术博客、游记博客优先</li>
<li>博客内容时常更新</li>
<li><strong>提交申请时若为https链接请带上https否则将视为http</strong></li>
</ul>
</div>
<nz-modal [(nzVisible)]="showModal" [nzTitle]="modalTitle" [nzContent]="modalContent" [nzFooter]="modalFooter"
(nzOnCancel)="cancel()">
<ng-template #modalTitle>
<h2 style="text-align: center">申请友链</h2>
</ng-template>
<ng-template #modalContent>
<div class="am-modal-bd">
<div class="article-setting">
<label>网站名称:
<input nz-input placeholder="请输入网站名称" nzSize="large" [(ngModel)]="link.name">
</label>
<br>
<br>
<label>网站链接:
<input nz-input placeholder="请输入网站链接" nzSize="large" [(ngModel)]="link.url">
</label>
</div>
</div>
</ng-template>
<ng-template #modalFooter>
<button nz-button (click)="cancel()">取消</button>
<button nz-button nzType="primary" (click)="apply()">提交</button>
</ng-template>
</nz-modal>

View File

@@ -0,0 +1,84 @@
i {
margin-right: 10px;
color: royalblue;
}
.title {
color: #909090
}
.site-middle {
width: 60%;
margin: 30px auto;
}
.partner-sites {
list-style: none;
display: flex;
flex-wrap: wrap;
padding: 20px 10px;
li {
width: 25%;
margin: 10px 0;
height: 30px;
line-height: 30px;
text-align: center
}
}
.placard {
width: 60%;
margin: 0 auto;
}
.placard-content {
margin-top: 30px;
margin-left: 30px;
border-left: 5px solid #aaa4a4;
list-style: none;
li {
padding-left: 15px;
font-size: 1.2em;
margin: 5px 0;
}
}
.applylink {
float: right;
border: none;
background: white;
border-radius: 5px;
width: 150px;
}
.applylink:hover {
cursor: pointer;
}
.title {
font-weight: bold;
font-size: 1.1em;
}
.contentinput {
margin-left: 8px;
width: 60%;
height: 32px;
background: #fafafa;
border: 1px solid #ddd;
border-radius: 4px;
}
@media only screen and ( max-width: 768px ) {
.site-middle, .placard {
margin-left: 2px;
width: 96%;
}
.partner-sites li {
float: left;
width: 100%;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LinkComponent } from './link.component';
describe('LinkComponent', () => {
let component: LinkComponent;
let fixture: ComponentFixture<LinkComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LinkComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LinkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,68 @@
import {Component, OnInit} from '@angular/core';
import {NzMessageService} from 'ng-zorro-antd';
import {Title} from '@angular/platform-browser';
import {ApiService} from '../../api/api.service';
import {Link} from '../../class/Link';
@Component({
selector: 'view-link',
templateUrl: './link.component.html',
styleUrls: ['./link.component.less']
})
export class LinkComponent implements OnInit {
constructor(private message: NzMessageService,
private titleService: Title,
private apiService: ApiService) {
titleService.setTitle('小海博客 | 友链');
}
showModal = false;
// 申请时的链接
link: Link;
linkList: Link[];
ngOnInit() {
window.scrollTo(0, 0);
this.link = new Link();
this.apiService.links().subscribe(data => {
this.linkList = data.result;
},
error => {
this.message.error(error.msg);
});
}
apply() {
if (this.link.name === '') {
this.message.error('网站名称不能为空');
return;
}
if (this.link.url === '') {
this.message.error('网站链接不能为空');
return;
}
const regExp = /^(https:\/\/|http:\/\/|)([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$/;
if (!regExp.test(this.link.url)) {
this.message.error('网站链接输入不合法');
return;
}
this.showModal = false;
this.apiService.applyLink(this.link).subscribe(data => {
this.message.success('提交成功,请稍等,即将为你处理');
},
error => {
this.message.error('提交失败,原因:' + error.msg);
});
}
cancel() {
this.showModal = false;
this.link.name = null;
this.link.url = null;
}
}

View File

@@ -0,0 +1,22 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {LinkComponent} from './link.component';
import {LinkRoutingModule} from './link-routing.module';
import {NzButtonModule, NzIconModule, NzInputModule, NzModalModule} from 'ng-zorro-antd';
import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [LinkComponent],
imports: [
CommonModule,
LinkRoutingModule,
NzIconModule,
NzModalModule,
FormsModule,
NzButtonModule,
NzInputModule
]
})
export class LinkModule {
}

View File

@@ -0,0 +1,30 @@
<div class="bruce flex-ct-x">
<form class="bubble-distribution">
<h3>登录</h3>
<div class="emailDiv">
<input type="email" name="email" [(ngModel)]="loginReq.email" placeholder="请输入邮箱"
pattern="^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$"
required>
<img class="img" src="https://oss.celess.cn/web/greeting.1415c1c.png" alt="">
</div>
<div class="pwdDiv">
<input type="password" [(ngModel)]="loginReq.password" name="pwd" placeholder="请输入密码(6到16位字符)"
pattern="^[\w_-]{6,16}$"
required>
<img class="img" src="https://oss.celess.cn/web/blindfold.58ce423.png" alt="">
</div>
<br>
<input type="checkbox" id="isRemember" name="isRemember" [(ngModel)]="loginReq.isRememberMe"> <label
for="isRemember"> 记住密码</label>
<span id="sendEmail" *ngIf="showSendEmail" (click)="sendEmail()">找回密码</span>
<span [style]="{display: showSendEmail?'block':'inline',float:showSendEmail?null:'right'}" class="to-reg">还没有账号,去
<a [routerLink]="'/user/registration'"
(click)="loginStatus.emit(true)">注册</a>
</span>
<img class="img" src="https://oss.celess.cn/web/normal.0447fe9.png" alt="">
<button nz-button nzType="primary" (click)="doLogin()" [nzLoading]="submitting">登录</button>
</form>
</div>

View File

@@ -0,0 +1,9 @@
@import "../../login-registration.component.less";
#sendEmail {
float: right;
}
.to-reg {
line-height: 40px;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,81 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {NzMessageService} from 'ng-zorro-antd';
import {LoginReq} from '../../../../class/User';
import {ActivatedRoute, Router} from '@angular/router';
import {LoginRegistrationService} from '../../service/login-registration.service';
import {Title} from '@angular/platform-browser';
import {GlobalUserService} from '../../../../services/global-user.service';
@Component({
selector: 'c-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.less']
})
export class LoginComponent implements OnInit {
constructor(private nzMessageService: NzMessageService,
private userService: GlobalUserService,
private activatedRoute: ActivatedRoute,
private router: Router,
private loginRegistrationService: LoginRegistrationService,
private title: Title) {
this.title.setTitle('小海博客 | 登录 ');
}
submitting: boolean = false;
loginReq: LoginReq = new LoginReq(null, true, null);
@Output() loginStatus = new EventEmitter<boolean>();
@Input() showSendEmail: boolean = true;
private url: string;
ngOnInit() {
this.url = this.activatedRoute.snapshot.queryParamMap.get('url');
this.loginReq.email = localStorage.getItem('e');
this.loginReq.password = localStorage.getItem('p');
localStorage.removeItem('e');
localStorage.removeItem('p');
}
doLogin() {
this.submitting = true;
const emailReg = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/;
const pwdReg = /^[\w_-]{6,16}$/;
if (!this.loginReq.email || !emailReg.test(this.loginReq.email)) {
this.submitting = false;
this.nzMessageService.error('邮箱格式不正确');
return;
}
if (!this.loginReq.password || !pwdReg.test(this.loginReq.password)) {
this.submitting = false;
this.nzMessageService.error('密码格式不正确');
return;
}
this.userService.login(this.loginReq, {
complete: () => null,
error: (err) => {
this.nzMessageService.error(err.msg);
this.submitting = false;
this.loginStatus.emit(false);
},
next: data => {
this.submitting = false;
this.nzMessageService.success('登录成功,欢迎你' + data.result.displayName);
this.loginStatus.emit(true);
if (this.url) {
this.router.navigateByUrl(this.url);
} else {
// window.location.href = '/admin/';
this.router.navigateByUrl('/admin')
}
}
}
);
}
sendEmail() {
this.loginRegistrationService.showModal = true;
}
}

View File

@@ -0,0 +1,26 @@
<div class="bruce flex-ct-x">
<form class="bubble-distribution">
<h3>注册</h3>
<div class="emailDiv">
<input type="email" name="email" [(ngModel)]="email" placeholder="请输入邮箱"
pattern="^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$"
required>
<img class="img" src="https://oss.celess.cn/web/greeting.1415c1c.png" alt="">
</div>
<div class="pwdDiv">
<input type="password" [(ngModel)]="pwd" name="pwd" placeholder="请输入密码(6到16位字符)"
pattern="^[\w_-]{6,16}$"
required>
<img class="img" src="https://oss.celess.cn/web/blindfold.58ce423.png" alt="">
</div>
<div class="codeDiv">
<input type="text" name="code" placeholder="请输入验证码" pattern="^[\w]{4}$" maxLength="4"
[(ngModel)]="imgCode" required>
<img [src]="imgCodeUrl" id="imgCode" alt="imgcode" (click)="changeImg()" title="点击更换验证码">
<img class="img" src="https://oss.celess.cn/web/greeting.1415c1c.png" alt="">
</div>
<img class="img" src="https://oss.celess.cn/web/normal.0447fe9.png" alt="">
<br>
<button nz-button nzType="primary" (click)="doRegistration()" [nzLoading]="submitting">注册</button>
</form>
</div>

View File

@@ -0,0 +1 @@
@import "../../login-registration.component.less";

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RegistrationComponent } from './registration.component';
describe('RegistrationComponent', () => {
let component: RegistrationComponent;
let fixture: ComponentFixture<RegistrationComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RegistrationComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RegistrationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,104 @@
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
import {environment} from '../../../../../environments/environment';
import {ApiService} from '../../../../api/api.service';
import {NzMessageService} from 'ng-zorro-antd';
import {Router} from '@angular/router';
import {ErrDispatch} from '../../../../class/ErrDispatch';
import {RequestObj} from '../../../../class/HttpReqAndResp';
import {LoginReq} from '../../../../class/User';
import {Title} from '@angular/platform-browser';
@Component({
selector: 'c-registration',
templateUrl: './registration.component.html',
styleUrls: ['./registration.component.less'],
providers: [ApiService]
})
export class RegistrationComponent implements OnInit, ErrDispatch {
constructor(private apiService: ApiService,
private nzMessageService: NzMessageService,
private router: Router,
private title: Title) {
apiService.setErrDispatch(this);
this.title.setTitle('小海博客 | 注册');
}
imgCodeUrl: string;
imgCode: string;
email: string;
pwd: string;
submitting: boolean;
@Output() regStatus = new EventEmitter<boolean>();
@Output() regAccount = new EventEmitter<LoginReq>();
ngOnInit() {
this.imgCodeUrl = environment.host + '/imgCode';
this.submitting = false;
}
changeImg() {
this.imgCode = '';
this.imgCodeUrl = environment.host + '/imgCode?t=' + new Date().valueOf();
}
// 提交注册
doRegistration() {
this.submitting = true;
// 数据验证
if (!this.email || !this.pwd) {
this.nzMessageService.error('邮箱账号和密码不可为空');
this.submitting = false;
return;
}
const emailReg = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/;
if (!emailReg.test(this.email)) {
this.nzMessageService.error('邮箱格式不正确');
this.submitting = false;
return;
}
const pwdReg = /^[\w_-]{6,16}$/;
if (!pwdReg.test(this.pwd)) {
this.nzMessageService.error('密码格式不正确');
this.submitting = false;
return;
}
if (!this.imgCode || this.imgCode.length !== 4) {
this.nzMessageService.error('验证码不正确');
this.submitting = false;
return;
}
// 验证验证码
this.apiService.verifyImgCode(this.imgCode).subscribe(data => {
// 验证成功 注册
this.apiService.registration(this.email, this.pwd).subscribe(regData => {
localStorage.setItem('e', this.email);
localStorage.setItem('p', this.pwd);
this.email = '';
this.pwd = '';
this.imgCode = '';
this.submitting = false;
this.nzMessageService.success('注册成功,三秒后跳转登录页面');
this.regStatus.emit(true);
this.regAccount.emit(new LoginReq(this.email, true, this.pwd));
setTimeout(() => {
if (this.router) {
this.router.navigateByUrl('/user/login');
}
}, 3000);
}
);
},
);
}
errHandler(code: number, msg: string, request?: RequestObj): void {
this.nzMessageService.error('reg' + msg);
this.regStatus.emit(false);
}
}

View File

@@ -0,0 +1,28 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {LoginComponent} from './components/login/login.component';
import {RegistrationComponent} from './components/registration/registration.component';
import {LoginRegistrationComponent} from './login-registration.component';
const routes: Routes = [
{
path: '',
component: LoginRegistrationComponent,
children: [
{path: 'login', component: LoginComponent},
{path: 'registration', component: RegistrationComponent},
{path: '**', component: LoginComponent}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class LoginRegistrationRoutingModule {
}

View File

@@ -0,0 +1,26 @@
<div [style.background-image]="'url(' +picUrl+')'" id="main">
<div id="op-Area">
<router-outlet></router-outlet>
</div>
</div>
<nz-modal [(nzVisible)]="loginRegistrationService.showModal" [nzTitle]="title" [nzContent]="content" [nzFooter]="null"
(nzOnCancel)="loginRegistrationService.showModal = false">
<ng-template #title>
<h2>发送密码重置邮件</h2>
</ng-template>
<ng-template #content>
<form nz-form>
<nz-form-item>
<input nz-input placeholder="请输入你的邮箱地址" [(ngModel)]="email" name="email">
</nz-form-item>
<nz-form-item>
<button nz-button style="width: 100%" nzType="primary" type="submit" (click)="send()"
[nzLoading]="submitting">发送邮件
</button>
</nz-form-item>
</form>
</ng-template>
</nz-modal>

View File

@@ -0,0 +1,114 @@
.bubble-distribution {
position: relative;
padding: 25px;
border-radius: 2px;
background-color: #fff;
h3 {
font-weight: bold;
font-size: 16px;
color: #333;
}
div {
margin-top: 10px;
}
.img {
position: absolute;
left: 50%;
bottom: 100%;
margin: 0 0 -20px -60px;
width: 120px;
}
input[type=email],
input[type=text],
input[type=password] {
padding: 10px;
outline: none;
border: 1px solid #e9e9e9;
border-radius: 2px;
width: 100%;
height: 40px;
transition: all 300ms;
&:focus:valid {
border-color: #09f;
}
&:focus:invalid {
border-color: #f66;
}
}
button {
width: 100%;
margin-top: 10px;
}
}
.emailDiv,
.pwdDiv,
.codeDiv {
.img {
display: none;
margin-bottom: -27px;
}
&:focus-within {
.img {
display: block;
}
& ~ .img {
display: none;
}
}
}
.codeDiv {
display: flex;
justify-content: space-between;
input {
width: 70% !important;
}
#imgCode {
margin-top: 0;
width: 25%;
height: 40px;
}
}
#main {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-position: center center;
background-size: cover;
#op-Area {
min-width: 320px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
@media screen and (max-width: 940px) {
#op-Area {
width: 90%;
margin:0 2%;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginRegistrationComponent } from './login-registration.component';
describe('LoginRegistrationComponent', () => {
let component: LoginRegistrationComponent;
let fixture: ComponentFixture<LoginRegistrationComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginRegistrationComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginRegistrationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,45 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../api/api.service';
import {LoginRegistrationService} from './service/login-registration.service';
import {NzMessageService} from 'ng-zorro-antd';
@Component({
selector: 'view-login-registration',
templateUrl: './login-registration.component.html',
styleUrls: ['./login-registration.component.less']
})
export class LoginRegistrationComponent implements OnInit {
constructor(private apiService: ApiService,
public loginRegistrationService: LoginRegistrationService,
private nzMessageService: NzMessageService) {
}
picUrl: string = '';
email: string;
submitting: boolean = false;
ngOnInit() {
this.apiService.bingPic().subscribe(data => {
this.picUrl = data.result;
});
}
send() {
this.submitting = true;
if (!this.email || this.email.length === 0) {
this.submitting = false;
this.nzMessageService.warning('邮箱不可为空');
return;
}
this.apiService.sendResetPwdEmail(this.email).subscribe(data => {
this.submitting = false;
this.nzMessageService.success('发送成功');
this.loginRegistrationService.showModal = false;
}, error => {
this.nzMessageService.error(error.msg);
this.submitting = false;
});
}
}

View File

@@ -0,0 +1,31 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {LoginComponent} from './components/login/login.component';
import {RegistrationComponent} from './components/registration/registration.component';
import {LoginRegistrationRoutingModule} from './login-registration-routing.module';
import {LoginRegistrationComponent} from './login-registration.component';
import {FormsModule} from '@angular/forms';
import {NzButtonModule, NzFormModule, NzGridModule, NzInputModule, NzModalModule} from 'ng-zorro-antd';
@NgModule({
declarations: [LoginComponent, RegistrationComponent, LoginRegistrationComponent],
exports: [
LoginComponent,
RegistrationComponent
],
imports: [
CommonModule,
LoginRegistrationRoutingModule,
FormsModule,
NzButtonModule,
NzModalModule,
NzFormModule,
NzInputModule,
NzGridModule
]
})
export class LoginRegistrationModule {
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { LoginRegistrationService } from './login-registration.service';
describe('LoginRegistrationService', () => {
let service: LoginRegistrationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LoginRegistrationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

Some files were not shown because too many files have changed in this diff Show More