重构界面ui
This commit is contained in:
18
index/src/app/view/article/article-routing.module.ts
Normal file
18
index/src/app/view/article/article-routing.module.ts
Normal 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 {
|
||||
}
|
||||
109
index/src/app/view/article/article.component.html
Normal file
109
index/src/app/view/article/article.component.html
Normal 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>
|
||||
|
||||
113
index/src/app/view/article/article.component.less
Normal file
113
index/src/app/view/article/article.component.less
Normal 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%
|
||||
}
|
||||
}
|
||||
25
index/src/app/view/article/article.component.spec.ts
Normal file
25
index/src/app/view/article/article.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
108
index/src/app/view/article/article.component.ts
Normal file
108
index/src/app/view/article/article.component.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
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/CommentReq';
|
||||
import {PageList} from '../../class/pageList';
|
||||
import {Comment} from '../../class/Comment';
|
||||
|
||||
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 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.apiService.userInfo().subscribe(data => {
|
||||
this.user = data.result;
|
||||
this.avatarImgUrl = data.result.avatarImgUrl;
|
||||
}, error => {
|
||||
});
|
||||
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, // 默认不解析
|
||||
sequenceDiagram: 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;
|
||||
}
|
||||
}
|
||||
42
index/src/app/view/article/article.module.ts
Normal file
42
index/src/app/view/article/article.module.ts
Normal 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 {
|
||||
}
|
||||
18
index/src/app/view/category/category-routing.module.ts
Normal file
18
index/src/app/view/category/category-routing.module.ts
Normal 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 {
|
||||
}
|
||||
28
index/src/app/view/category/category.component.html
Normal file
28
index/src/app/view/category/category.component.html
Normal 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>
|
||||
29
index/src/app/view/category/category.component.less
Normal file
29
index/src/app/view/category/category.component.less
Normal 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;
|
||||
}
|
||||
}
|
||||
25
index/src/app/view/category/category.component.spec.ts
Normal file
25
index/src/app/view/category/category.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
71
index/src/app/view/category/category.component.ts
Normal file
71
index/src/app/view/category/category.component.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {Category} from '../../class/Category';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {PageList} from '../../class/pageList';
|
||||
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: Category[] = [];
|
||||
private category: Category;
|
||||
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: Category) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
20
index/src/app/view/category/category.module.ts
Normal file
20
index/src/app/view/category/category.module.ts
Normal 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 {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<div>
|
||||
<div id="main">
|
||||
<nz-alert [nzType]="type" [nzMessage]="message"
|
||||
[nzDescription]="desc" nzShowIcon>
|
||||
</nz-alert>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
#main{
|
||||
width: 60%;
|
||||
height: 50px;
|
||||
margin: 0 auto;
|
||||
padding: 15% 0;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
49
index/src/app/view/email-verify/email-verify.component.ts
Normal file
49
index/src/app/view/email-verify/email-verify.component.ts
Normal 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
24
index/src/app/view/email-verify/email-verify.module.ts
Normal file
24
index/src/app/view/email-verify/email-verify.module.ts
Normal 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 {
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
18
index/src/app/view/index/index-routing.module.ts
Normal file
18
index/src/app/view/index/index-routing.module.ts
Normal 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 {
|
||||
}
|
||||
100
index/src/app/view/index/index.component.html
Normal file
100
index/src/app/view/index/index.component.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<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="lastestUpdateTime">
|
||||
<i nz-icon nzType="info-circle" nzTheme="outline"></i>上次更新时间: {{lastestUpdateTime}}
|
||||
</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>
|
||||
|
||||
96
index/src/app/view/index/index.component.less
Normal file
96
index/src/app/view/index/index.component.less
Normal file
@@ -0,0 +1,96 @@
|
||||
@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 {
|
||||
#index-bloger-container {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
|
||||
#index-bloger-desc {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#index-left {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
margin: 0 5%;
|
||||
}
|
||||
|
||||
#index-right {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
margin: 0 5%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关于博主 栏小于1420会错位故提前将其设置为居中
|
||||
@media screen and (max-width: 1420px) {
|
||||
#index-bloger-container {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
25
index/src/app/view/index/index.component.spec.ts
Normal file
25
index/src/app/view/index/index.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
105
index/src/app/view/index/index.component.ts
Normal file
105
index/src/app/view/index/index.component.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
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/pageList';
|
||||
import {ErrDispatch} from '../../class/ErrDispatch';
|
||||
import {RequestObj} from '../../class/Request';
|
||||
import {Router} from '@angular/router';
|
||||
import {Category} from '../../class/Category';
|
||||
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: Category[];
|
||||
counts: {
|
||||
articleCount: number,
|
||||
visitorCount: number,
|
||||
categoryCount: number,
|
||||
leaveMsgCount: number,
|
||||
tagCount: number,
|
||||
commentCount: number
|
||||
};
|
||||
lastestUpdateTime: 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.lastestUpdateTime().subscribe({
|
||||
next: data => this.lastestUpdateTime = 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));
|
||||
}
|
||||
}
|
||||
44
index/src/app/view/index/index.module.ts
Normal file
44
index/src/app/view/index/index.module.ts
Normal 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 {
|
||||
|
||||
}
|
||||
17
index/src/app/view/leave-msg/leave-msg-routing.module.ts
Normal file
17
index/src/app/view/leave-msg/leave-msg-routing.module.ts
Normal 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 {
|
||||
}
|
||||
1
index/src/app/view/leave-msg/leave-msg.component.html
Normal file
1
index/src/app/view/leave-msg/leave-msg.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<p>leave-msg works!</p>
|
||||
25
index/src/app/view/leave-msg/leave-msg.component.spec.ts
Normal file
25
index/src/app/view/leave-msg/leave-msg.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
15
index/src/app/view/leave-msg/leave-msg.component.ts
Normal file
15
index/src/app/view/leave-msg/leave-msg.component.ts
Normal 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() {
|
||||
}
|
||||
|
||||
}
|
||||
15
index/src/app/view/leave-msg/leave-msg.module.ts
Normal file
15
index/src/app/view/leave-msg/leave-msg.module.ts
Normal 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 {
|
||||
}
|
||||
18
index/src/app/view/link/link-routing.module.ts
Normal file
18
index/src/app/view/link/link-routing.module.ts
Normal 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 {
|
||||
}
|
||||
52
index/src/app/view/link/link.component.html
Normal file
52
index/src/app/view/link/link.component.html
Normal 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>
|
||||
84
index/src/app/view/link/link.component.less
Normal file
84
index/src/app/view/link/link.component.less
Normal 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%;
|
||||
}
|
||||
}
|
||||
25
index/src/app/view/link/link.component.spec.ts
Normal file
25
index/src/app/view/link/link.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
68
index/src/app/view/link/link.component.ts
Normal file
68
index/src/app/view/link/link.component.ts
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
22
index/src/app/view/link/link.module.ts
Normal file
22
index/src/app/view/link/link.module.ts
Normal 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 {
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,9 @@
|
||||
@import "../../login-registration.component.less";
|
||||
|
||||
#sendEmail {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.to-reg {
|
||||
line-height: 40px;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {ApiService} from '../../../../api/api.service';
|
||||
import {LoginReq} from '../../../../class/LoginReq';
|
||||
import {LocalStorageService} from '../../../../utils/local-storage.service';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {LoginRegistrationService} from '../../service/login-registration.service';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'c-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.less']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
constructor(private nzMessageService: NzMessageService,
|
||||
private apiService: ApiService,
|
||||
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.apiService.login(this.loginReq).subscribe(
|
||||
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/';
|
||||
}
|
||||
}, error => {
|
||||
this.nzMessageService.error(error.msg);
|
||||
this.submitting = false;
|
||||
this.loginStatus.emit(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sendEmail() {
|
||||
this.loginRegistrationService.showModal = true;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
@import "../../login-registration.component.less";
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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/Request';
|
||||
import {LoginReq} from '../../../../class/LoginReq';
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LoginRegistrationService {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
showModal: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="error404">
|
||||
<div class="wenzi">
|
||||
<div class="wenzi-1">亲,您已经飘到外太空了哦!</div>
|
||||
<div class="wenzi_2">想回地球请点击:<a href="/">小海博客</a></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,52 @@
|
||||
div{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
font-family: 'Microsoft Yahei', '\65B0\5B8B\4F53', '\5B8B\4F53', Verdana
|
||||
}
|
||||
|
||||
.error404 {
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
height: 460px;
|
||||
background: url(../../../assets/img/404.jpg) 50px;
|
||||
background-size: 100% 100%;
|
||||
margin: 3px auto 0
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00c0ff !important;
|
||||
}
|
||||
|
||||
.wenzi {
|
||||
padding-top: 326px;
|
||||
text-align: center;
|
||||
font-family: "微软雅黑";
|
||||
font-size: 26px;
|
||||
color: #686e6e
|
||||
}
|
||||
|
||||
.wenzi {
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
color: #888888;
|
||||
display: block;
|
||||
border-bottom: 1px solid #d0cfcf
|
||||
}
|
||||
|
||||
.wenzi_2 span {
|
||||
color: #319898;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
@media only screen and (max-width:768px) {
|
||||
.error404 {
|
||||
width: 100%;
|
||||
background: url(../../../assets/img/404-m.jpg) -10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PageNotFoundComponent } from './page-not-found.component';
|
||||
|
||||
describe('PageNotFoundComponent', () => {
|
||||
let component: PageNotFoundComponent;
|
||||
let fixture: ComponentFixture<PageNotFoundComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PageNotFoundComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PageNotFoundComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'view-page-not-found',
|
||||
templateUrl: './page-not-found.component.html',
|
||||
styleUrls: ['./page-not-found.component.less']
|
||||
})
|
||||
export class PageNotFoundComponent implements OnInit {
|
||||
|
||||
constructor(private title: Title) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.title.setTitle('小海博客 | 404 page not found');
|
||||
}
|
||||
|
||||
}
|
||||
18
index/src/app/view/page-not-found/page-not-found.module.ts
Normal file
18
index/src/app/view/page-not-found/page-not-found.module.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {PageNotFoundComponent} from './page-not-found.component';
|
||||
import {Route, RouterModule} from '@angular/router';
|
||||
|
||||
const routes: Route[] = [
|
||||
{path: '**', component: PageNotFoundComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [PageNotFoundComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(routes)
|
||||
]
|
||||
})
|
||||
export class PageNotFoundModule {
|
||||
}
|
||||
14
index/src/app/view/reset-pwd/reset-pwd.component.html
Normal file
14
index/src/app/view/reset-pwd/reset-pwd.component.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<div style="height: 70%">
|
||||
<div id="main">
|
||||
<div *ngIf="iserror">
|
||||
<nz-alert nzType="error" nzMessage="链接可能被修改了,请重新点击邮箱中的链接,或者重新发送邮件" nzShowIcon>
|
||||
</nz-alert>
|
||||
</div>
|
||||
<div *ngIf="!iserror">
|
||||
<input type="password" placeholder="新密码" [(ngModel)]="pwd"/>
|
||||
<input type="password" placeholder="确认密码" [(ngModel)]="rePwd"/>
|
||||
<button (click)="submit()">提交</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
29
index/src/app/view/reset-pwd/reset-pwd.component.less
Normal file
29
index/src/app/view/reset-pwd/reset-pwd.component.less
Normal file
@@ -0,0 +1,29 @@
|
||||
#main {
|
||||
background: #ffffff;
|
||||
border-radius: 5px;
|
||||
width: 410px;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 5px;
|
||||
|
||||
button {
|
||||
display: block;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: #9FFE8E;
|
||||
height: 40px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
border-radius: 5px;
|
||||
height: 40px;
|
||||
margin-bottom: 10px;
|
||||
background: #eeeeee
|
||||
}
|
||||
}
|
||||
25
index/src/app/view/reset-pwd/reset-pwd.component.spec.ts
Normal file
25
index/src/app/view/reset-pwd/reset-pwd.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ResetPwdComponent } from './reset-pwd.component';
|
||||
|
||||
describe('ResetPwdComponent', () => {
|
||||
let component: ResetPwdComponent;
|
||||
let fixture: ComponentFixture<ResetPwdComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ResetPwdComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ResetPwdComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
69
index/src/app/view/reset-pwd/reset-pwd.component.ts
Normal file
69
index/src/app/view/reset-pwd/reset-pwd.component.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'view-reset-pwd',
|
||||
templateUrl: './reset-pwd.component.html',
|
||||
styleUrls: ['./reset-pwd.component.less']
|
||||
})
|
||||
export class ResetPwdComponent implements OnInit {
|
||||
|
||||
constructor(private message: NzMessageService,
|
||||
private router: Router,
|
||||
private routerinfo: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
private title: Title) {
|
||||
this.title.setTitle('小海博客 | 重置密码 ');
|
||||
}
|
||||
|
||||
pwd: string;
|
||||
rePwd: string;
|
||||
|
||||
private email: string;
|
||||
private verifyId: string;
|
||||
|
||||
iserror: boolean = false;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.email = this.routerinfo.snapshot.queryParams.email;
|
||||
this.verifyId = this.routerinfo.snapshot.queryParams.verifyId;
|
||||
if (this.email == null || this.verifyId == null) {
|
||||
this.iserror = true;
|
||||
}
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.pwd == null) {
|
||||
return;
|
||||
}
|
||||
if (this.pwd.length > 16) {
|
||||
this.message.error('密码过长');
|
||||
return;
|
||||
}
|
||||
if (this.pwd.length < 6) {
|
||||
this.message.error('密码过短');
|
||||
return;
|
||||
}
|
||||
if (this.pwd !== this.rePwd) {
|
||||
this.message.warning('两次密码不一致');
|
||||
return;
|
||||
}
|
||||
this.apiService.resetPwd(this.verifyId, this.email, this.pwd).subscribe({
|
||||
next: data => {
|
||||
this.message.success('重置密码成功,5秒后转跳到登录页面。');
|
||||
setTimeout(() => {
|
||||
this.router.navigateByUrl('/user/login');
|
||||
// window.location.href = '/login';
|
||||
}, 5000);
|
||||
|
||||
},
|
||||
error: e => {
|
||||
this.message.error(e.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
25
index/src/app/view/reset-pwd/reset-pwd.module.ts
Normal file
25
index/src/app/view/reset-pwd/reset-pwd.module.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Route, RouterModule} from '@angular/router';
|
||||
import {ResetPwdComponent} from './reset-pwd.component';
|
||||
import {NzAlertModule} from 'ng-zorro-antd';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
const routes: Route[] = [
|
||||
{path: '**', component: ResetPwdComponent}
|
||||
];
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ResetPwdComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(routes),
|
||||
NzAlertModule,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class ResetPwdModule {
|
||||
}
|
||||
18
index/src/app/view/tag/tag-routing.module.ts
Normal file
18
index/src/app/view/tag/tag-routing.module.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {TagComponent} from './tag.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: ':tag', component: TagComponent},
|
||||
{path: '**', component: TagComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
|
||||
export class TagRoutingModule {
|
||||
}
|
||||
28
index/src/app/view/tag/tag.component.html
Normal file
28
index/src/app/view/tag/tag.component.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<div id="main">
|
||||
<div id="category">
|
||||
<ul *ngIf="tagList.length">
|
||||
<c-tag-tag *ngFor="let tag of tagList"
|
||||
[tag]="tag"
|
||||
(tagClick)="changeTag(tag)">
|
||||
|
||||
</c-tag-tag>
|
||||
</ul>
|
||||
<h2 *ngIf="!tagList.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>
|
||||
|
||||
|
||||
30
index/src/app/view/tag/tag.component.less
Normal file
30
index/src/app/view/tag/tag.component.less
Normal file
@@ -0,0 +1,30 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
25
index/src/app/view/tag/tag.component.spec.ts
Normal file
25
index/src/app/view/tag/tag.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TagComponent } from './tag.component';
|
||||
|
||||
describe('TagComponent', () => {
|
||||
let component: TagComponent;
|
||||
let fixture: ComponentFixture<TagComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TagComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TagComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
72
index/src/app/view/tag/tag.component.ts
Normal file
72
index/src/app/view/tag/tag.component.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Category} from '../../class/Category';
|
||||
import {PageList} from '../../class/pageList';
|
||||
import {Article} from '../../class/Article';
|
||||
import {Tag} from '../../class/Tag';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {Location} from '@angular/common';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'view-tag',
|
||||
templateUrl: './tag.component.html',
|
||||
styleUrls: ['./tag.component.less']
|
||||
})
|
||||
export class TagComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService,
|
||||
private nzMessageService: NzMessageService,
|
||||
private location: Location,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private title: Title) {
|
||||
}
|
||||
|
||||
tagList: { name: string, size: number } [] = [];
|
||||
private tag: { name: string, size: number };
|
||||
articleList: PageList<Article>;
|
||||
|
||||
name: string;
|
||||
|
||||
ngOnInit() {
|
||||
this.name = this.activatedRoute.snapshot.paramMap.get('tag');
|
||||
this.getTags(this.name == null);
|
||||
if (this.name != null) {
|
||||
this.getArticles(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
getTags(needGetArticle: boolean) {
|
||||
this.apiService.tagsNac().subscribe(data => {
|
||||
this.tagList = data.result;
|
||||
this.tag = data.result[0];
|
||||
if (needGetArticle) {
|
||||
this.getArticles(this.tag.name);
|
||||
this.name = this.tag.name;
|
||||
this.title.setTitle('小海博客 | 标签 | ' + this.name);
|
||||
}
|
||||
}, error => {
|
||||
this.nzMessageService.error('出现了错误,原因:' + error.msg);
|
||||
});
|
||||
}
|
||||
|
||||
getArticles(tagName: string) {
|
||||
this.apiService.articlesByTag(tagName).subscribe(data => {
|
||||
this.articleList = data.result;
|
||||
}, error => {
|
||||
this.nzMessageService.error('出现了错误,原因:' + error.msg);
|
||||
});
|
||||
}
|
||||
|
||||
changeTag(tag: { name: string, size: number }) {
|
||||
if (this.name === tag.name) {
|
||||
return;
|
||||
}
|
||||
this.tag = tag;
|
||||
this.name = tag.name;
|
||||
this.location.replaceState('tags/' + this.name);
|
||||
this.getArticles(this.name);
|
||||
this.title.setTitle('小海博客 | 标签 | ' + this.name);
|
||||
}
|
||||
}
|
||||
17
index/src/app/view/tag/tag.module.ts
Normal file
17
index/src/app/view/tag/tag.module.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {TagComponent} from './tag.component';
|
||||
import {TagRoutingModule} from './tag-routing.module';
|
||||
import {IndexModule} from '../index/index.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [TagComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
TagRoutingModule,
|
||||
IndexModule
|
||||
]
|
||||
})
|
||||
export class TagModule {
|
||||
}
|
||||
33
index/src/app/view/update/update.component.html
Normal file
33
index/src/app/view/update/update.component.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<div id="app">
|
||||
<!--页面主体-->
|
||||
<div id="main">
|
||||
<div class="container">
|
||||
<div class="zh-update">
|
||||
<div class="zh-update-log">
|
||||
<div class="zh-update-log-title">
|
||||
<h1>更新日志</h1>
|
||||
<p>最后更新于 {{lastUpdateTime}}</p>
|
||||
</div>
|
||||
<div class="zh-update-log-content">
|
||||
|
||||
|
||||
<div class="update-log am-animation-slide-bottom" *ngFor="let update of webUpdate">
|
||||
<div class="update-log-date">
|
||||
<h2><span style="font-size: x-large"># </span> {{update.time}}</h2>
|
||||
</div>
|
||||
<div class="update-log-content">
|
||||
<blockquote>
|
||||
<p *ngFor="let item of update.info.split('\n')">{{item}}</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
139
index/src/app/view/update/update.component.less
Normal file
139
index/src/app/view/update/update.component.less
Normal file
@@ -0,0 +1,139 @@
|
||||
@media screen and (min-width: 768px) {
|
||||
.site-inner {
|
||||
margin: 0 19%;
|
||||
}
|
||||
|
||||
.word p {
|
||||
text-indent: 7em;
|
||||
}
|
||||
|
||||
.word {
|
||||
padding: 20px 25px;
|
||||
}
|
||||
|
||||
.zh-update {
|
||||
margin: 0 30%;
|
||||
}
|
||||
|
||||
.update-leave-message {
|
||||
margin: 100px 10%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.site-inner {
|
||||
margin: 0 5%;
|
||||
}
|
||||
|
||||
.word p {
|
||||
text-indent: 2em;
|
||||
}
|
||||
|
||||
.word {
|
||||
padding: 20px 5px;
|
||||
}
|
||||
|
||||
.zh-update {
|
||||
margin: 0 3%;
|
||||
}
|
||||
}
|
||||
|
||||
.am-g {
|
||||
margin: 50px 0 0;
|
||||
}
|
||||
|
||||
.site-inner-img {
|
||||
text-align: center;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.site-inner-img img {
|
||||
width: 450px;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.site-inner-mywords {
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.word {
|
||||
margin-top: 10px;
|
||||
margin-left: 25%;
|
||||
font-size: 21px;
|
||||
line-height: 20px;
|
||||
color: blue;
|
||||
font-family: 华文仿宋;
|
||||
}
|
||||
|
||||
.zh-update-log-title {
|
||||
margin: 50px 0;
|
||||
}
|
||||
|
||||
.zh-update-log-title h1 {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #222;
|
||||
text-indent: 1em;
|
||||
}
|
||||
|
||||
.zh-update-log-title p {
|
||||
text-indent: 1.5em;
|
||||
}
|
||||
|
||||
.zh-update-log-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.update-log i {
|
||||
color: #999999;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.update-log h2 {
|
||||
display: inline-block;
|
||||
font-size: 1.3em;
|
||||
font-weight: 650;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.update-log-content {
|
||||
margin: 15px 26px;
|
||||
}
|
||||
|
||||
.update-log-content blockquote {
|
||||
padding: 0 10px 0 20px;
|
||||
border-left: 4px solid #ddd;
|
||||
}
|
||||
|
||||
.update-log-content p {
|
||||
font-size: 16px;
|
||||
word-wrap: break-word;
|
||||
font-family: 微软雅黑;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5 r
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
//.bg {
|
||||
// background: url("https://oss.celess.cn/web/morning.jpg");
|
||||
// background-position: center center;
|
||||
// background-size: cover;
|
||||
//}
|
||||
|
||||
//.bg:after {
|
||||
// content: "";
|
||||
// position: absolute;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// background: inherit;
|
||||
// filter: blur(2px);
|
||||
// z-index: 2;
|
||||
//}
|
||||
25
index/src/app/view/update/update.component.spec.ts
Normal file
25
index/src/app/view/update/update.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UpdateComponent } from './update.component';
|
||||
|
||||
describe('UpdateComponent', () => {
|
||||
let component: UpdateComponent;
|
||||
let fixture: ComponentFixture<UpdateComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ UpdateComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UpdateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
29
index/src/app/view/update/update.component.ts
Normal file
29
index/src/app/view/update/update.component.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'view-update',
|
||||
templateUrl: './update.component.html',
|
||||
styleUrls: ['./update.component.less']
|
||||
})
|
||||
export class UpdateComponent implements OnInit {
|
||||
|
||||
constructor(private titleService: Title,
|
||||
private apiService: ApiService) {
|
||||
titleService.setTitle('小海博客 | 网站更新记录');
|
||||
}
|
||||
|
||||
lastUpdateTime: string;
|
||||
webUpdate: { id: number, info: string, time: string }[] = [];
|
||||
|
||||
ngOnInit() {
|
||||
this.apiService.lastestUpdateTime().subscribe(data => {
|
||||
this.lastUpdateTime = data.result;
|
||||
});
|
||||
this.apiService.webUpdate().subscribe(data => {
|
||||
this.webUpdate = data.result.reverse();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
16
index/src/app/view/update/update.module.ts
Normal file
16
index/src/app/view/update/update.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {UpdateComponent} from './update.component';
|
||||
import {Route, RouterModule} from '@angular/router';
|
||||
|
||||
const routes: Route[] = [{path: '**', component: UpdateComponent}];
|
||||
|
||||
@NgModule({
|
||||
declarations: [UpdateComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(routes)
|
||||
]
|
||||
})
|
||||
export class UpdateModule {
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<form nz-form [formGroup]="formGroup" (ngSubmit)="publishArticle()">
|
||||
<!-- 文章类型 -->
|
||||
<nz-form-item *ngIf="primaryData.id">
|
||||
<nz-form-label nzSpan="4">发布为</nz-form-label>
|
||||
<nz-form-control nzSpan="19" nzOffset="1" nzErrorTip="请选择文章发布类型">
|
||||
<nz-radio-group formControlName="isUpdate">
|
||||
<label nz-radio-button [nzValue]="false"><span>新文章</span></label>
|
||||
<label nz-radio-button [nzValue]="true"><span>更新旧文章</span></label>
|
||||
</nz-radio-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<!-- 文章类型 -->
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="4">文章类型</nz-form-label>
|
||||
<nz-form-control nzSpan="19" nzOffset="1" nzErrorTip="请选择文章的类型">
|
||||
<nz-radio-group formControlName="type" (ngModelChange)="articleTypeChanged()">
|
||||
<label nz-radio-button [nzValue]="true"><span>原创</span></label>
|
||||
<label nz-radio-button [nzValue]="false"><span>转载</span></label>
|
||||
</nz-radio-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<!-- 标签 -->
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="4" (click)="showTagInput()">文章标签</nz-form-label>
|
||||
<nz-form-control [nzSpan]="19" [nzOffset]="1">
|
||||
<input formControlName="tagList" style="display: none">
|
||||
<nz-tag *ngFor="let t of tagTmpList" nzMode="closeable"
|
||||
(nzOnClose)="handleClose(t)">{{t}}</nz-tag>
|
||||
<nz-tag *ngIf="!tagInputVisible&&tagTmpList.length<5" (click)="showTagInput()">
|
||||
<i nz-icon nzType="plus"></i> New Tag
|
||||
</nz-tag>
|
||||
<input #inputElement nz-input nzSize="small" *ngIf="tagInputVisible" formControlName="tags"
|
||||
type="text" style="width: 78px;" (blur)="handleTagInputConfirm()"
|
||||
(keydown.enter)="handleTagInputConfirm()">
|
||||
|
||||
<div *ngIf="tagListTouched&&formGroup.get('tagList').hasError('required')"
|
||||
class="errTip">未选择或添加标签
|
||||
</div>
|
||||
|
||||
<nz-card nzSize="small" nzTitle="已有标签" title="最多可选五个标签">
|
||||
<nz-tag *ngFor="let tag of tagNacList"
|
||||
style="margin: 4px; border: 1px dashed #6A6A6A;user-select: none"
|
||||
[nzMode]="(tagTmpList.length<5||tagTmpList.indexOf(tag.name) > -1)?'checkable':'default'"
|
||||
[nzChecked]="tagTmpList.indexOf(tag.name) > -1"
|
||||
(nzCheckedChange)="tagChange($event,tag.name)">
|
||||
{{ tag.name }}
|
||||
</nz-tag>
|
||||
</nz-card>
|
||||
|
||||
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<!-- 文章分类 -->
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="4">文章分类</nz-form-label>
|
||||
<nz-form-control nzSpan="19" nzOffset="1" nzErrorTip="文章的分类不可为空">
|
||||
<nz-select nzAllowClear nzPlaceHolder="选择分类" formControlName="category">
|
||||
<nz-option *ngFor="let option of categoryList" [nzValue]="option.name"
|
||||
[nzLabel]="option.name"></nz-option>
|
||||
</nz-select>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<!-- 原文链接 -->
|
||||
<nz-form-item *ngIf="!formGroup.value.type">
|
||||
<nz-form-label nzSpan="4">原文链接</nz-form-label>
|
||||
<nz-form-control nzSpan="19" nzOffset="1">
|
||||
<nz-input-group [nzSuffix]="close">
|
||||
<input nz-input type="url" placeholder="请输入原文连接" formControlName="url">
|
||||
</nz-input-group>
|
||||
<ng-template #close>
|
||||
<i *ngIf="formGroup.value.url" nz-icon nzType="close-circle" nzTheme="fill"
|
||||
(click)="clearInput()"></i>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="formGroup.get('url').touched&&formGroup.get('url').hasError('required')"
|
||||
class="errTip">原文链接不可为空
|
||||
</div>
|
||||
<div *ngIf="formGroup.get('url').touched&&formGroup.get('url').hasError('pattern')"
|
||||
class="errTip">原文链接不合法
|
||||
</div>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<button nz-button style="width: 100%" nzType="primary"
|
||||
type="submit" [disabled]="!formGroup.valid">提交
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.errTip {
|
||||
color: #f5222d;
|
||||
line-height: 30px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TestComponent } from './publish-form.component';
|
||||
|
||||
describe('TestComponent', () => {
|
||||
let component: TestComponent;
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TestComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
|
||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||
import {ApiService} from '../../../../api/api.service';
|
||||
import {Category} from '../../../../class/Category';
|
||||
|
||||
@Component({
|
||||
selector: 'c-publish-form',
|
||||
templateUrl: './publish-form.component.html',
|
||||
styleUrls: ['./publish-form.component.less']
|
||||
})
|
||||
export class PublishFormComponent implements OnInit {
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
}
|
||||
|
||||
@ViewChild('inputElement', {static: false}) tagInputElement: ElementRef;
|
||||
|
||||
@Input() tagNacList: { name: string, size: number }[];
|
||||
@Input() categoryList: Category[];
|
||||
@Input() primaryData: { id: number, type: boolean, tags: string, category: string, url?: string };
|
||||
@Output() submitEvent = new EventEmitter<{ id: number, type: boolean, tags: string, category: string, url?: string }>();
|
||||
|
||||
formGroup: FormGroup;
|
||||
tagTmpList: string[] = [];
|
||||
tagInputVisible: boolean = false;
|
||||
tagListTouched: boolean = false;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.formGroup = this.fb.group({
|
||||
isUpdate: [true, Validators.required],
|
||||
type: [true, Validators.required],
|
||||
tagList: [null, Validators.required],
|
||||
tags: [null],
|
||||
category: [null, Validators.required],
|
||||
url: [null]
|
||||
});
|
||||
if (this.primaryData) {
|
||||
this.formGroup.get('type').setValue(this.primaryData.type);
|
||||
const tags = this.primaryData.tags;
|
||||
this.formGroup.get('tagList').setValue(tags ? this.primaryData.tags.split(',') : tags);
|
||||
this.tagTmpList = tags ? this.primaryData.tags.split(',') : [...tags];
|
||||
this.formGroup.get('category').setValue(this.primaryData.category);
|
||||
this.formGroup.get('url').setValue(this.primaryData.url);
|
||||
}
|
||||
}
|
||||
|
||||
publishArticle() {
|
||||
const formData = {
|
||||
id: this.formGroup.value.isUpdate ? this.primaryData.id : null,
|
||||
type: this.formGroup.value.type,
|
||||
tags: this.formGroup.value.tagList.toString(),
|
||||
category: this.formGroup.value.category,
|
||||
url: this.formGroup.value.type ? null : this.formGroup.value.url
|
||||
};
|
||||
this.submitEvent.emit(formData);
|
||||
}
|
||||
|
||||
// 点击New Tag 显示输入框
|
||||
showTagInput() {
|
||||
this.tagInputVisible = true;
|
||||
setTimeout(() => {
|
||||
this.tagInputElement.nativeElement.focus();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// 点击标签右侧的x
|
||||
handleClose(t: string) {
|
||||
this.tagTmpList = this.tagTmpList.filter(tag => tag !== t);
|
||||
this.formGroup.get('tagList').setValue(this.tagTmpList);
|
||||
}
|
||||
|
||||
// 输入框输入完成
|
||||
handleTagInputConfirm() {
|
||||
this.tagListTouched = true;
|
||||
const tmpTag = this.formGroup.get('tags').value;
|
||||
if (tmpTag && this.tagTmpList.indexOf(tmpTag) === -1) {
|
||||
this.tagTmpList = [...this.tagTmpList, tmpTag];
|
||||
}
|
||||
this.formGroup.get('tags').setValue('');
|
||||
this.formGroup.get('tagList').setValue(this.tagTmpList.length ? this.tagTmpList : null);
|
||||
this.formGroup.get('tagList').updateValueAndValidity();
|
||||
this.tagInputVisible = false;
|
||||
}
|
||||
|
||||
// 点击tag切换
|
||||
tagChange($event: boolean, name: string) {
|
||||
this.tagListTouched = true;
|
||||
if (this.tagTmpList.indexOf(name) > -1) {
|
||||
this.tagTmpList = this.tagTmpList.filter(v => v !== name);
|
||||
} else {
|
||||
this.tagTmpList.push(name);
|
||||
}
|
||||
this.formGroup.get('tagList').setValue(this.tagTmpList.length ? this.tagTmpList : null);
|
||||
this.formGroup.get('tagList').updateValueAndValidity();
|
||||
}
|
||||
|
||||
// 清除url内容
|
||||
clearInput() {
|
||||
this.formGroup.get('url').setValue('');
|
||||
}
|
||||
|
||||
// 点选了文章类型
|
||||
articleTypeChanged() {
|
||||
this.formGroup.get(`url`).clearValidators();
|
||||
const type = this.formGroup.get(`type`).value;
|
||||
this.formGroup.get(`url`).setValidators(type ? null : [Validators.required, Validators.pattern('^(https:\/\/|http:\/\/|)([\\w-]+\\.)+[\\w-]+(\\/[\\w-./?%&=]*)?$')]);
|
||||
this.formGroup.get(`url`).updateValueAndValidity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { EditorMdDirective } from './editor-md.directive';
|
||||
|
||||
describe('EditorMdDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
const directive = new EditorMdDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
||||
31
index/src/app/view/write/editor-md/editor-md.directive.ts
Normal file
31
index/src/app/view/write/editor-md/editor-md.directive.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {AfterViewInit, Attribute, Directive, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {EditorConfig} from '../../../class/EditorConfig';
|
||||
|
||||
declare var editormd: any;
|
||||
declare var $: any;
|
||||
|
||||
@Directive({
|
||||
selector: '[appEditorMd]'
|
||||
})
|
||||
export class EditorMdDirective implements AfterViewInit {
|
||||
|
||||
@Input() editormdConfig: EditorConfig; // 配置选项
|
||||
@Output() onEditorChange: EventEmitter<string> = new EventEmitter<string>(); // 发射器
|
||||
editor: any; // editormd编辑器
|
||||
|
||||
constructor(@Attribute('id') private id: string) {
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.editor = editormd(this.id, this.editormdConfig); // 创建编辑器
|
||||
|
||||
const out = this.onEditorChange;
|
||||
const textarea = $('#' + this.id + ' :first'); // 获取textarea元素
|
||||
|
||||
// 当编辑器内容改变时,触发textarea的change事件
|
||||
// tslint:disable-next-line:only-arrow-functions
|
||||
this.editor.on('change', function() {
|
||||
out.emit(textarea.val());
|
||||
});
|
||||
}
|
||||
}
|
||||
25
index/src/app/view/write/write.component.html
Normal file
25
index/src/app/view/write/write.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="main">
|
||||
<div class="con">
|
||||
<input type="text" [(ngModel)]="article.title" id="title" placeholder="文章标题">
|
||||
<button nz-button nzType="primary" id="submit" (click)="articleSubmit()">提交</button>
|
||||
</div>
|
||||
|
||||
<div id="md" appEditorMd [editormdConfig]="conf" (onEditorChange)="syncModel($event)">
|
||||
<textarea style="display: block;" [(ngModel)]="article.mdContent"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nz-modal [(nzVisible)]="modalVisible" [nzTitle]="title" [nzContent]="content" [nzFooter]="null"
|
||||
(nzOnCancel)="modalVisible = false">
|
||||
|
||||
<ng-template #title>
|
||||
<h3 style="text-align: center">文章发布</h3>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #content>
|
||||
<c-publish-form [categoryList]="categoryList" [tagNacList]="tagNacList" [primaryData]="primaryData"
|
||||
(submitEvent)="publishArticle($event)">
|
||||
</c-publish-form>
|
||||
</ng-template>
|
||||
|
||||
</nz-modal>
|
||||
34
index/src/app/view/write/write.component.less
Normal file
34
index/src/app/view/write/write.component.less
Normal file
@@ -0,0 +1,34 @@
|
||||
.main {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 70px;
|
||||
bottom: 0;
|
||||
|
||||
.con {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
#title {
|
||||
/* 85% */
|
||||
flex-grow: 1;
|
||||
margin: 0 20px 5px 20px;
|
||||
height: 35px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
#submit {
|
||||
display: flex;
|
||||
flex-wrap: wrap
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#md {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
}
|
||||
25
index/src/app/view/write/write.component.spec.ts
Normal file
25
index/src/app/view/write/write.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WriteComponent } from './write.component';
|
||||
|
||||
describe('WriteComponent', () => {
|
||||
let component: WriteComponent;
|
||||
let fixture: ComponentFixture<WriteComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ WriteComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(WriteComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
197
index/src/app/view/write/write.component.ts
Normal file
197
index/src/app/view/write/write.component.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||
import {ArticleReq} from '../../class/ArticleReq';
|
||||
import {EditorConfig} from '../../class/EditorConfig';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {User} from '../../class/User';
|
||||
import {Category} from '../../class/Category';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'view-write',
|
||||
templateUrl: './write.component.html',
|
||||
styleUrls: ['./write.component.less']
|
||||
})
|
||||
export class WriteComponent implements OnInit {
|
||||
|
||||
constructor(private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
private message: NzMessageService,
|
||||
private titleService: Title) {
|
||||
this.titleService.setTitle('小海博客 | 创作');
|
||||
}
|
||||
|
||||
modalVisible: boolean = false;
|
||||
conf = new EditorConfig();
|
||||
articleId: number;
|
||||
isUpdate = false;
|
||||
|
||||
public article: ArticleReq = new ArticleReq();
|
||||
|
||||
userInfo: User;
|
||||
categoryList: Category[];
|
||||
tagNacList: { name: string, size: number }[];
|
||||
primaryData = {};
|
||||
// 发布新文章时,文章相同会被拦回 此处判断一下
|
||||
title: string;
|
||||
|
||||
// 同步属性内容
|
||||
syncModel(str): void {
|
||||
this.article.mdContent = str;
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.articleId = this.activatedRoute.snapshot.queryParams.id;
|
||||
if (this.articleId != null) {
|
||||
this.isUpdate = true;
|
||||
this.getArticle();
|
||||
}
|
||||
if (!this.articleId && localStorage.getItem('tmpArticle')) {
|
||||
this.article = JSON.parse(localStorage.getItem('tmpArticle'));
|
||||
}
|
||||
this.setSuitableHeight();
|
||||
// 用户权限判断
|
||||
this.apiService.userInfo().subscribe({
|
||||
next: data => {
|
||||
this.userInfo = data.result;
|
||||
if (data.result.role !== 'admin') {
|
||||
this.message.info('你暂时无发布文章的权限,所写文章将暂存在本地');
|
||||
}
|
||||
},
|
||||
error: e => {
|
||||
this.message.info('你暂时还没有登录,请点击右上角登录后开始创作');
|
||||
}
|
||||
});
|
||||
this.apiService.tagsNac().subscribe(data => {
|
||||
this.tagNacList = data.result;
|
||||
this.tagNacList.sort((a, b) => a.name.length - b.name.length);
|
||||
});
|
||||
this.apiService.categories().subscribe({
|
||||
next: data => {
|
||||
this.categoryList = data.result;
|
||||
},
|
||||
error: err => {
|
||||
this.message.error('获取分类信息失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置高度
|
||||
*/
|
||||
setSuitableHeight() {
|
||||
this.conf.height = (window.innerHeight - 120) + '';
|
||||
}
|
||||
|
||||
// 提交按钮的事件
|
||||
articleSubmit() {
|
||||
this.modalVisible = true;
|
||||
if (this.article.title === '' || this.article.mdContent === '') {
|
||||
this.message.warning(this.article.title === '' ? '标题不能为空' : '文章不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章数据提交
|
||||
*/
|
||||
publishArticle(e: { id: number, type: boolean, tags: string, category: string, url?: string }) {
|
||||
this.article.type = e.type;
|
||||
this.article.tags = e.tags;
|
||||
this.article.category = e.category;
|
||||
this.article.url = e.url;
|
||||
this.article.id = e.id;
|
||||
|
||||
if (!this.article.id && this.title === this.article.title) {
|
||||
this.message.error('文章标题未经更改,请修改后再发布');
|
||||
return;
|
||||
}
|
||||
|
||||
// 文章 暂存
|
||||
localStorage.setItem('tmpArticle', JSON.stringify(this.article));
|
||||
|
||||
this.article.url = this.article.type ? null : this.article.url;
|
||||
|
||||
if (!this.isUpdate) {
|
||||
// 非文章更新
|
||||
|
||||
this.apiService.createArticle(this.article).subscribe({
|
||||
next: data => {
|
||||
// TODO 成功
|
||||
this.message.success('发布成功,即将转跳');
|
||||
localStorage.removeItem('tmpArticle');
|
||||
|
||||
setTimeout(() => {
|
||||
this.router.navigateByUrl('article/' + data.result.id);
|
||||
}, 2500);
|
||||
},
|
||||
error: err => {
|
||||
if (err.code === 3010) {
|
||||
// 未登陆
|
||||
this.router.navigateByUrl('/user/login');
|
||||
}
|
||||
if (err.code === 3020) {
|
||||
this.message.error('你没有发布文章的权限,文章替你暂存在本地');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// 文章更新
|
||||
|
||||
this.apiService.updateArticle(this.article).subscribe({
|
||||
next: data => {
|
||||
this.message.success('更新成功,即将转跳');
|
||||
localStorage.removeItem('tmpArticle');
|
||||
setTimeout(() => {
|
||||
this.router.navigateByUrl('article/' + data.result.id);
|
||||
}, 2500);
|
||||
},
|
||||
error: e => {
|
||||
if (e.code === 3010) {
|
||||
this.router.navigateByUrl('login');
|
||||
} else if (e.code === 3020) {
|
||||
this.message.error('你没有更新文章的权限');
|
||||
} else {
|
||||
this.message.error('失败,原因:' + e.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章 for update
|
||||
*/
|
||||
getArticle() {
|
||||
this.apiService.getArticle(this.articleId, true).subscribe({
|
||||
next: data => {
|
||||
this.article.category = data.result.category;
|
||||
this.article.mdContent = data.result.mdContent;
|
||||
this.article.tags = String(data.result.tags);
|
||||
this.article.type = data.result.original;
|
||||
this.article.url = data.result.url;
|
||||
this.article.title = data.result.title;
|
||||
this.article.id = data.result.id;
|
||||
this.title = data.result.title;
|
||||
this.primaryData = {
|
||||
type: this.article.type,
|
||||
tags: this.article.tags,
|
||||
category: this.article.category,
|
||||
url: this.article.url,
|
||||
id: this.article.id
|
||||
};
|
||||
},
|
||||
error: e => {
|
||||
if (e.code === 2010) {
|
||||
// 文章不存在
|
||||
this.message.error('文章不存在');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
44
index/src/app/view/write/write.module.ts
Normal file
44
index/src/app/view/write/write.module.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {WriteComponent} from './write.component';
|
||||
import {Route, RouterModule} from '@angular/router';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {EditorMdDirective} from './editor-md/editor-md.directive';
|
||||
import {PublishFormComponent} from './components/publish-form/publish-form.component';
|
||||
import {
|
||||
NzButtonModule, NzCardModule, NzDividerModule,
|
||||
NzFormModule,
|
||||
NzIconModule,
|
||||
NzInputModule,
|
||||
NzModalModule,
|
||||
NzRadioModule,
|
||||
NzSelectModule,
|
||||
NzTagModule
|
||||
} from 'ng-zorro-antd';
|
||||
|
||||
const routes: Route[] = [
|
||||
{path: '**', component: WriteComponent}
|
||||
];
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [WriteComponent, EditorMdDirective, PublishFormComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(routes),
|
||||
FormsModule,
|
||||
NzButtonModule,
|
||||
NzModalModule,
|
||||
NzFormModule,
|
||||
ReactiveFormsModule,
|
||||
NzInputModule,
|
||||
NzTagModule,
|
||||
NzIconModule,
|
||||
NzRadioModule,
|
||||
NzSelectModule,
|
||||
NzCardModule,
|
||||
NzDividerModule,
|
||||
]
|
||||
})
|
||||
export class WriteModule {
|
||||
}
|
||||
Reference in New Issue
Block a user