从"Blog"仓库中分离出来
This commit is contained in:
41
index/src/app/app-routing.module.ts
Normal file
41
index/src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {Routes, RouterModule} from '@angular/router';
|
||||
|
||||
import {IndexComponent} from './pages/index/index.component';
|
||||
import {UpdateComponent} from './pages/update/update.component';
|
||||
import {CategoryComponent} from './pages/categories/category.component';
|
||||
import {TagComponent} from './pages/tag/tag.component';
|
||||
import {LeaveMsgComponent} from './pages/leave-msg/leave-msg.component';
|
||||
import {PartnerSitesComponent} from './pages/partner-sites/partner-sites.component';
|
||||
import {ArticleComponent} from './pages/article/article.component';
|
||||
import {EmailVerifyComponent} from './pages/email-verify/email-verify.component';
|
||||
import {ResetPwdComponent} from './pages/reset-pwd/reset-pwd.component';
|
||||
import {NotFoundComponent} from './pages/not-found/not-found.component';
|
||||
import {WriteComponent} from './pages/write/write.component';
|
||||
import {LoginComponent} from './pages/login/login.component';
|
||||
import {RegistrationComponent} from './pages/registration/registration.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: IndexComponent},
|
||||
{path: 'update', component: UpdateComponent},
|
||||
{path: 'category', component: CategoryComponent},
|
||||
{path: 'tag', component: TagComponent},
|
||||
{path: 'leaveMsg', component: LeaveMsgComponent},
|
||||
{path: 'links', component: PartnerSitesComponent},
|
||||
{path: 'article/:id', component: ArticleComponent},
|
||||
{path: 'write', component: WriteComponent},
|
||||
{path: 'resetPwd', component: ResetPwdComponent},
|
||||
{path: 'emailVerify', component: EmailVerifyComponent},
|
||||
{path: 'login', component: LoginComponent},
|
||||
{path: 'registration', component: RegistrationComponent},
|
||||
{path: '404', component: NotFoundComponent},
|
||||
{path: '**', component: NotFoundComponent}
|
||||
];
|
||||
|
||||
// TODO lazyLoad
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule {
|
||||
}
|
||||
0
index/src/app/app.component.css
Normal file
0
index/src/app/app.component.css
Normal file
21
index/src/app/app.component.html
Normal file
21
index/src/app/app.component.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<app-header></app-header>
|
||||
<div style="height: 80px"></div>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<div style="height: 60px"></div>
|
||||
<app-footer></app-footer>
|
||||
|
||||
<nz-modal [(nzVisible)]="userService.loginModalVisible" nzClosable="true"
|
||||
(nzOnCancel)="userService.loginModalVisible=false" [nzFooter]="footerButton">
|
||||
<ng-template [ngIf]="userService.loginModalType=='registration'">
|
||||
<app-registration></app-registration>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="userService.loginModalType=='login'">
|
||||
<app-login></app-login>
|
||||
</ng-template>
|
||||
</nz-modal>
|
||||
|
||||
<ng-template #footerButton>
|
||||
<small style="width:100%;text-align: center">小海博客</small>
|
||||
</ng-template>
|
||||
35
index/src/app/app.component.spec.ts
Normal file
35
index/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'blog'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('blog');
|
||||
});
|
||||
|
||||
it('should render title in a h1 tag', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to blog!');
|
||||
});
|
||||
});
|
||||
16
index/src/app/app.component.ts
Normal file
16
index/src/app/app.component.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {UserService} from './services/user/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'blog';
|
||||
|
||||
constructor(public userService: UserService) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
92
index/src/app/app.module.ts
Normal file
92
index/src/app/app.module.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {NgZorroAntdModule, NZ_I18N, zh_CN} from 'ng-zorro-antd';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {HttpClientModule} from '@angular/common/http';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {registerLocaleData} from '@angular/common';
|
||||
import zh from '@angular/common/locales/zh';
|
||||
|
||||
import {EditorMdDirective} from './pages/write/editor/editor-md.directive';
|
||||
|
||||
// components
|
||||
import {HeaderComponent} from './components/header/header.component';
|
||||
import {FooterComponent} from './components/footer/footer.component';
|
||||
|
||||
// pages
|
||||
import {IndexComponent} from './pages/index/index.component';
|
||||
import {UpdateComponent} from './pages/update/update.component';
|
||||
import {CategoryComponent} from './pages/categories/category.component';
|
||||
import {TagComponent} from './pages/tag/tag.component';
|
||||
import {LeaveMsgComponent} from './pages/leave-msg/leave-msg.component';
|
||||
import {PartnerSitesComponent} from './pages/partner-sites/partner-sites.component';
|
||||
import {ArticleComponent} from './pages/article/article.component';
|
||||
import {EmailVerifyComponent} from './pages/email-verify/email-verify.component';
|
||||
import {ResetPwdComponent} from './pages/reset-pwd/reset-pwd.component';
|
||||
import {NotFoundComponent} from './pages/not-found/not-found.component';
|
||||
import {LoginComponent} from './pages/login/login.component';
|
||||
import {RegistrationComponent} from './pages/registration/registration.component';
|
||||
import {WriteComponent} from './pages/write/write.component';
|
||||
|
||||
// services
|
||||
import {HttpService} from './services/http.service';
|
||||
import {UserService} from './services/user/user.service';
|
||||
import {ArticleService} from './services/article/article.service';
|
||||
import {CategoryService} from './services/category/category.service';
|
||||
import {CountService} from './services/count/count.service';
|
||||
import {CommentService} from './services/comment/comment.service';
|
||||
import {TagService} from './services/tag/tag.service';
|
||||
import {WebUpdateService} from './services/update/web-update.service';
|
||||
import {LinkService} from './services/link/link.service';
|
||||
|
||||
registerLocaleData(zh);
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeaderComponent,
|
||||
FooterComponent,
|
||||
EditorMdDirective,
|
||||
|
||||
IndexComponent,
|
||||
UpdateComponent,
|
||||
CategoryComponent,
|
||||
TagComponent,
|
||||
LeaveMsgComponent,
|
||||
PartnerSitesComponent,
|
||||
ArticleComponent,
|
||||
EmailVerifyComponent,
|
||||
ResetPwdComponent,
|
||||
NotFoundComponent,
|
||||
LoginComponent,
|
||||
RegistrationComponent,
|
||||
WriteComponent
|
||||
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
NgZorroAntdModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
BrowserAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
{provide: NZ_I18N, useValue: zh_CN},
|
||||
HttpService,
|
||||
UserService,
|
||||
ArticleService,
|
||||
CountService,
|
||||
CommentService,
|
||||
TagService,
|
||||
WebUpdateService,
|
||||
CategoryService,
|
||||
LinkService
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
11
index/src/app/class/LeaveMsg.ts
Normal file
11
index/src/app/class/LeaveMsg.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export class LeaveMsg {
|
||||
id: number;
|
||||
type: number;
|
||||
authorName: string;
|
||||
authorAvatarImgUrl: string;
|
||||
content: string;
|
||||
date: string;
|
||||
pid: number;
|
||||
responseId: string;
|
||||
child: LeaveMsg[];
|
||||
}
|
||||
19
index/src/app/class/article.ts
Normal file
19
index/src/app/class/article.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export class Article {
|
||||
id: number;
|
||||
title: string;
|
||||
summary: string;
|
||||
mdContent?: string;
|
||||
original?: boolean;
|
||||
url?: string;
|
||||
publishDateFormat?: string;
|
||||
updateDateFormat?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
authorName?: string;
|
||||
preArticleId?: number;
|
||||
nextArticleId?: number;
|
||||
preArticleTitle?: string;
|
||||
nextArticleTitle?: string;
|
||||
readingNumber?: number;
|
||||
open?: string;
|
||||
}
|
||||
14
index/src/app/class/articleReq.ts
Normal file
14
index/src/app/class/articleReq.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export class ArticleReq {
|
||||
category: string;
|
||||
id?: number;
|
||||
mdContent: string;
|
||||
open: boolean;
|
||||
tags: string;
|
||||
title: string;
|
||||
type: boolean;
|
||||
url?: string;
|
||||
|
||||
constructor() {
|
||||
this.type = true;
|
||||
}
|
||||
}
|
||||
6
index/src/app/class/category.ts
Normal file
6
index/src/app/class/category.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class Category {
|
||||
id: number;
|
||||
name: string;
|
||||
articles: string;
|
||||
size?: number;
|
||||
}
|
||||
18
index/src/app/class/commentReq.ts
Normal file
18
index/src/app/class/commentReq.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export class CommentReq {
|
||||
articleID: number;
|
||||
comment: boolean;
|
||||
content: string;
|
||||
id: number;
|
||||
pid: number;
|
||||
responseId: string;
|
||||
|
||||
constructor(comment: boolean) {
|
||||
this.comment = comment;
|
||||
this.responseId = '';
|
||||
if (!comment) {
|
||||
this.articleID = -1;
|
||||
}
|
||||
this.pid = -1;
|
||||
this.id = null;
|
||||
}
|
||||
}
|
||||
7
index/src/app/class/count.ts
Normal file
7
index/src/app/class/count.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export class Count {
|
||||
visitorCount: number = 0;
|
||||
leaveMsgCount: number = 0;
|
||||
tagCount: number = 0;
|
||||
articleCount: number = 0;
|
||||
commentCount: number = 0;
|
||||
}
|
||||
6
index/src/app/class/data.ts
Normal file
6
index/src/app/class/data.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class Data {
|
||||
code: number;
|
||||
msg: string;
|
||||
result: any;
|
||||
date: number;
|
||||
}
|
||||
30
index/src/app/class/editor-config.ts
Normal file
30
index/src/app/class/editor-config.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {environment} from '../../environments/environment';
|
||||
|
||||
export class EditorConfig {
|
||||
public width = '100%';
|
||||
public height = '400';
|
||||
public path = 'assets/editor.md/lib/';
|
||||
public codeFold: true;
|
||||
public searchReplace = true;
|
||||
public toolbar = true;
|
||||
public placeholder = '欢迎来到小海的创作中心';
|
||||
public emoji = true;
|
||||
public taskList = true;
|
||||
public tex = true;
|
||||
public readOnly = false;
|
||||
public tocm = true;
|
||||
public watch = true;
|
||||
public previewCodeHighlight = true;
|
||||
public saveHTMLToTextarea = true;
|
||||
public markdown = '';
|
||||
public flowChart = true;
|
||||
public syncScrolling = true;
|
||||
public sequenceDiagram = true;
|
||||
public imageUpload = true;
|
||||
public imageFormats = ['jpg', 'jpeg', 'gif', 'png', 'bmp', 'webp'];
|
||||
public imageUploadURL = environment.host + '/imgUpload';
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
5
index/src/app/class/loginReq.ts
Normal file
5
index/src/app/class/loginReq.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class LoginReq {
|
||||
email: string;
|
||||
password: string;
|
||||
isRememberMe: boolean = false;
|
||||
}
|
||||
20
index/src/app/class/page.ts
Normal file
20
index/src/app/class/page.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export class Page<T> {
|
||||
total: number;
|
||||
list: T[];
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
size: number;
|
||||
startRow: number;
|
||||
endRow: number;
|
||||
pages: number;
|
||||
prePage: number;
|
||||
nextPage: number;
|
||||
isFirstPage: boolean;
|
||||
isLastPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
hasNextPage: boolean;
|
||||
navigatePages: number;
|
||||
navigatepageNums: number[];
|
||||
navigateFirstPage: number;
|
||||
navigateLastPage: number;
|
||||
}
|
||||
6
index/src/app/class/tag.ts
Normal file
6
index/src/app/class/tag.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class Tag {
|
||||
id?: number;
|
||||
name: string;
|
||||
articles?: string;
|
||||
size: number;
|
||||
}
|
||||
5
index/src/app/class/updateInfo.ts
Normal file
5
index/src/app/class/updateInfo.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class UpdateInfo {
|
||||
id: number;
|
||||
info: string;
|
||||
time: string;
|
||||
}
|
||||
9
index/src/app/class/user.ts
Normal file
9
index/src/app/class/user.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class User {
|
||||
id: number;
|
||||
email: string;
|
||||
displayName: string;
|
||||
emailStatus: boolean;
|
||||
avatarImgUrl: string;
|
||||
desc: string;
|
||||
role: string;
|
||||
}
|
||||
22
index/src/app/components/footer/footer.component.css
Normal file
22
index/src/app/components/footer/footer.component.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.footer {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
background-color: #eeeeee;
|
||||
text-align: center;
|
||||
color: #009688;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
/* 设置z-index 是为了write页面将footer隐藏*/
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #ffffff;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #009688 !important;
|
||||
}
|
||||
11
index/src/app/components/footer/footer.component.html
Normal file
11
index/src/app/components/footer/footer.component.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="footer">
|
||||
<hr>
|
||||
<div>
|
||||
<a href="http://www.miitbeian.gov.cn" target="_blank">
|
||||
鄂ICP备18023929号
|
||||
</a>
|
||||
<div>
|
||||
© 2019 <a href="https://www.celess.cn">小海博客</a> - <i class="fa fa-coffee" style="margin: 0 5px"></i>郑海 版权所有
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
25
index/src/app/components/footer/footer.component.spec.ts
Normal file
25
index/src/app/components/footer/footer.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FooterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
15
index/src/app/components/footer/footer.component.ts
Normal file
15
index/src/app/components/footer/footer.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrls: ['./footer.component.css']
|
||||
})
|
||||
export class FooterComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
136
index/src/app/components/header/header.component.css
Normal file
136
index/src/app/components/header/header.component.css
Normal file
@@ -0,0 +1,136 @@
|
||||
header {
|
||||
height: 50px;
|
||||
background-color: #ffffff;
|
||||
opacity: 0.8;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div {
|
||||
float: left;
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin: 5px 5px 5px 30px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.headerButton {
|
||||
border: none;
|
||||
background: #3F66FF;
|
||||
color: #ffffff !important;
|
||||
margin-right: 10px;
|
||||
border-radius: 5px;
|
||||
padding: 8px 13px;
|
||||
line-height: 50px
|
||||
}
|
||||
|
||||
#landr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#loged {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
#blogTitle,
|
||||
#desc {
|
||||
display: block;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#blogTitle :hover {
|
||||
text-decoration: none;
|
||||
color: #797979;
|
||||
}
|
||||
|
||||
#blogTitle {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#blogTitle :hover {
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
#desc {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
[nz-button] {
|
||||
margin-right: 8px;
|
||||
margin-top: 9px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
/**导航栏*/
|
||||
.nav-desktop {
|
||||
position: absolute;
|
||||
width: auto;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.nav-phone {
|
||||
display: none
|
||||
}
|
||||
|
||||
a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-desktop li {
|
||||
float: left;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.nav-desktop li:hover {
|
||||
background: #ececec;
|
||||
color: #797979;
|
||||
}
|
||||
|
||||
.nav-desktop li a {
|
||||
height: 100%;
|
||||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and ( max-width: 768px) {
|
||||
.nav-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-phone {
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.ul {
|
||||
width: 300px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.ul li {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #ececec
|
||||
}
|
||||
|
||||
#click-main {
|
||||
line-height: 50px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
67
index/src/app/components/header/header.component.html
Normal file
67
index/src/app/components/header/header.component.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<header>
|
||||
<a href="/">
|
||||
<div>
|
||||
<img src="https://56462271.oss-cn-beijing.aliyuncs.com/web/logo.png"/>
|
||||
</div>
|
||||
</a>
|
||||
<div>
|
||||
<a href="/" id="blogTitle"><span>小海博客</span></a>
|
||||
<span id="desc">记录学习成长历程</span>
|
||||
</div>
|
||||
|
||||
|
||||
<ul class="nav-desktop">
|
||||
<li><a class="top_bar" routerLink="/" style="cursor: pointer;"><i nz-icon nzType="home" nzTheme="fill"></i> 首页</a></li>
|
||||
<li><a class="top_bar" routerLink="/category" style="cursor: pointer;"><i nz-icon nzType="project" nzTheme="fill"></i> 分类</a>
|
||||
</li>
|
||||
<li><a class="top_bar" routerLink="/tag" style="cursor: pointer;"><i nz-icon nzType="tags" nzTheme="fill"></i> 标签</a>
|
||||
</li>
|
||||
<li><a class="top_bar" routerLink="/leaveMsg" style="cursor: pointer;"><i nz-icon nzType="profile" nzTheme="fill"></i> 留言</a>
|
||||
</li>
|
||||
<li><a class="top_bar" routerLink="/update" style="cursor: pointer;"><i nz-icon nzType="up-square" nzTheme="fill"></i> 更新</a>
|
||||
</li>
|
||||
<li><a class="top_bar" routerLink="/links" style="cursor: pointer;"><i nz-icon nzType="link" nzTheme="outline"></i> 友链</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
<div class="nav-phone">
|
||||
<a nz-dropdown [nzDropdownMenu]="menu" nzTrigger="click" [nzClickHide]="false" [(nzVisible)]="visibleOfMenu"
|
||||
id="click-main">
|
||||
<i nz-icon nzType="menu" nzTheme="outline"></i>
|
||||
</a>
|
||||
<nz-dropdown-menu #menu="nzDropdownMenu">
|
||||
<ul nz-menu class="ul">
|
||||
<li nz-menu-item><a class="top_bar" routerLink="/" style="cursor: pointer;"><i nz-icon nzType="home" nzTheme="fill"></i> 首页</a></li>
|
||||
<li nz-menu-item><a class="top_bar" routerLink="/category" style="cursor: pointer;"><i nz-icon nzType="project" nzTheme="fill"></i> 分类</a></li>
|
||||
<li nz-menu-item><a class="top_bar" routerLink="/tag" style="cursor: pointer;"><i nz-icon nzType="tags" nzTheme="fill"></i> 标签</a></li>
|
||||
<li nz-menu-item><a class="top_bar" routerLink="/leaveMsg" style="cursor: pointer;"><i nz-icon nzType="profile" nzTheme="fill"></i> 留言</a></li>
|
||||
<li nz-menu-item><a class="top_bar" routerLink="/update" style="cursor: pointer;"><i nz-icon nzType="up-square" nzTheme="fill"></i> 更新</a></li>
|
||||
<li nz-menu-item><a class="top_bar" routerLink="/links" style="cursor: pointer;"><i nz-icon nzType="link" nzTheme="outline"></i> 友链</a></li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="loged" *ngIf="userService.userInfo">
|
||||
<a nz-dropdown [nzDropdownMenu]="menu" nzTrigger="click" [nzClickHide]="false" [(nzVisible)]="visible">
|
||||
{{userService.userInfo.displayName ? userService.userInfo.displayName : userService.userInfo.email}}
|
||||
<i nz-icon nzType="down"></i>
|
||||
</a>
|
||||
<nz-dropdown-menu #menu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item><a href="/admin/">{{userService.userInfo.role == 'admin' ? '网站后台管理' : '个人中心'}}</a></li>
|
||||
<hr style="margin: 10px 0 5px 0;">
|
||||
<li nz-menu-item>
|
||||
<a (click)="logout()">退出登录</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="landr" *ngIf="!userService.userInfo">
|
||||
<button nz-button nzType="primary" (click)="userService.showModal('login')">登录</button>
|
||||
<button nz-button nzType="primary" (click)="userService.showModal('registration')">注册</button>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
25
index/src/app/components/header/header.component.spec.ts
Normal file
25
index/src/app/components/header/header.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HeaderComponent } from './header.component';
|
||||
|
||||
describe('HeaderComponent', () => {
|
||||
let component: HeaderComponent;
|
||||
let fixture: ComponentFixture<HeaderComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HeaderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
28
index/src/app/components/header/header.component.ts
Normal file
28
index/src/app/components/header/header.component.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.css']
|
||||
})
|
||||
export class HeaderComponent implements OnInit {
|
||||
|
||||
|
||||
constructor(public userService: UserService) {
|
||||
}
|
||||
|
||||
// 菜单是否可见
|
||||
public visible: boolean = false;
|
||||
// 导航是否可见(手机显示时)
|
||||
public visibleOfMenu: boolean = false;
|
||||
|
||||
ngOnInit() {
|
||||
this.userService.getUserInfo();
|
||||
this.userService.http.visit();
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.userService.logout();
|
||||
}
|
||||
}
|
||||
330
index/src/app/pages/article/article.component.css
Normal file
330
index/src/app/pages/article/article.component.css
Normal file
@@ -0,0 +1,330 @@
|
||||
/* markdown Css style */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.list-group li {
|
||||
/* margin-top: 10px;*/
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.arUl {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.arUl li {
|
||||
margin-right: 12px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.arUl li i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.arTypeOriginal {
|
||||
background: #5eb95e;
|
||||
}
|
||||
|
||||
.arTypeReprint {
|
||||
background: #F37B1D;
|
||||
}
|
||||
|
||||
.arType {
|
||||
color: #ffffff;
|
||||
font-size: 0.8em;
|
||||
padding: 2px 6px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.article-titlea {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.article-meta a {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.article-road {
|
||||
margin-left: 2%;
|
||||
}
|
||||
|
||||
.secd {
|
||||
margin-left: 30%;
|
||||
}
|
||||
|
||||
#title {
|
||||
word-break: break-all;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 2em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.article-text {
|
||||
word-break: break-all
|
||||
}
|
||||
|
||||
.article-tag {
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
|
||||
/*文章底部的tag标签*/
|
||||
|
||||
.tag {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.article-bAnda {
|
||||
width: 90%;
|
||||
margin-top: 2%;
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
|
||||
.articlePublishDate,
|
||||
.originalAuthor,
|
||||
.categories {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.article-input-textarea {
|
||||
border-color: #aaaaaa;
|
||||
border-width: 2px;
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
max-height: 700px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.comment-submit {
|
||||
float: right;
|
||||
color: white;
|
||||
background: #3bb4f2;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 3px 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.comment-list {
|
||||
margin: 50px 10% 20px;
|
||||
}
|
||||
|
||||
.comment-input {
|
||||
border-color: #ececec;
|
||||
border-width: 1px;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
margin-top: 21px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.article-comment {
|
||||
margin: 0 10%;
|
||||
}
|
||||
|
||||
.comment-input-coverText {
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
margin: 5px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.buttonDisable {
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.articleButton {
|
||||
background: none;
|
||||
border: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.articleButton:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.commentUser {
|
||||
background: #5eb95e;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
color: white
|
||||
}
|
||||
|
||||
.commentItem {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.loading img {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%)
|
||||
}
|
||||
|
||||
#pagination {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.tag i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.article-last,
|
||||
.article-next {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
.comment {
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#content p {
|
||||
word-break: break-all
|
||||
}
|
||||
|
||||
button {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding: 0 5%;
|
||||
}
|
||||
|
||||
.noToc{
|
||||
width: 65% !important;
|
||||
margin: 0 auto !important;
|
||||
height: auto;
|
||||
position: static !important;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
|
||||
.article-content {
|
||||
left: 400px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin-right: 500px;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.article-toc-main{
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
position: fixed;
|
||||
left: 40px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 320px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#article-toc{
|
||||
max-height: 700px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.article-content {
|
||||
width: 100%;
|
||||
margin-top: -80px;
|
||||
padding-top: 60px;
|
||||
margin-right:0;
|
||||
border-radius: 0;
|
||||
margin-left: 0!important;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.article-toc-main{
|
||||
position:relative;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
.noToc{
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
}
|
||||
149
index/src/app/pages/article/article.component.html
Normal file
149
index/src/app/pages/article/article.component.html
Normal file
@@ -0,0 +1,149 @@
|
||||
<ng-template [ngIf]="article">
|
||||
<nz-spin [nzSpinning]="!loadOk">
|
||||
|
||||
|
||||
<div class="article-content am-article" [ngClass]="{'noToc':!showToc}">
|
||||
<div style="text-align: center;word-break: break-all">
|
||||
<h1 class="article-title" id="title">{{article.title}}</h1>
|
||||
</div>
|
||||
<br>
|
||||
<ul class="arUl">
|
||||
<li>
|
||||
<span class="arType"
|
||||
[ngClass]="{'arTypeOriginal': article.original,'arTypeReprint':!article.original}">{{article.original ? "原创" : "转载"}}</span>
|
||||
</li>
|
||||
<li><i nz-icon nzType="calendar" nzTheme="outline"></i>{{article.publishDateFormat}}</li>
|
||||
<li><i nz-icon nzType="user" nzTheme="outline"></i>{{article.authorName}}</li>
|
||||
<li><i nz-icon nzType="folder" nzTheme="outline"></i>
|
||||
<a [routerLink]="['/category']" [queryParams]="{'name':article.category}">
|
||||
<span>{{article.category}}</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
<div class="article-text am-text-break am-article-bd">
|
||||
<!-- <div id="content" [innerHTML]="article.htmlContent">
|
||||
</div> -->
|
||||
<div class="article-toc-main" *ngIf="showToc">
|
||||
<!-- fixme: 不同文章切换时 无法生效 -->
|
||||
<span><strong>目录:</strong></span><br>
|
||||
<div id="article-toc" class="markdown-body editormd-preview-container">
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
|
||||
<span id="over">over</span>
|
||||
</div>
|
||||
<!-- 文章版权 -->
|
||||
<div id="copyright">
|
||||
<p>本文作者:{{article.authorName}} </p>
|
||||
<p>{{article.original ? "本文" : "原文"}}链接:{{article.original ? copyRightUrl : article.url}}</p>
|
||||
<p>版权声明:转载请注明出处</p>
|
||||
</div>
|
||||
|
||||
|
||||
<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]="['/tag']" [queryParams]="{name:item}">{{item}}</a>
|
||||
</span>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="article-bAnda">
|
||||
<span class="article-last">
|
||||
<button (click)="toArticle(article.nextArticleId)" class="articleButton"
|
||||
[ngClass]="{'buttonDisable': article.nextArticleId==-1}">
|
||||
<i nz-icon nzType="caret-up"
|
||||
nzTheme="outline"></i> 上一篇文章:{{article.nextArticleTitle}}</button>
|
||||
</span>
|
||||
<span class="article-next">
|
||||
<button (click)="toArticle(article.preArticleId)" class="articleButton"
|
||||
[ngClass]="{'buttonDisable': article.preArticleId==-1}">
|
||||
<i nz-icon nzType="caret-down"
|
||||
nzTheme="outline"></i> 下一篇文章篇文章:{{article.preArticleTitle}}</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<span *ngIf="!userService.userInfo" style="display: block;text-align: center">若要评论,请先
|
||||
<a style="color: blue !important;text-decoration: underline"
|
||||
(click)="userService.showModal('login')">登录</a>哟~~~~</span>
|
||||
<nz-comment *ngIf="userService.userInfo" style="width: 60%;margin:15px auto">
|
||||
<nz-comment-content>
|
||||
<nz-form-item>
|
||||
<textarea [(ngModel)]="comment4submit" nz-input style="height: 130px;width: 100%;"></textarea>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button nzType="primary" [disabled]="!comment4submit" (click)="submitComment()">评论</button>
|
||||
</nz-form-item>
|
||||
</nz-comment-content>
|
||||
</nz-comment>
|
||||
|
||||
<!-- 展示 -->
|
||||
<nz-card id="leaveMsgs" [nzLoading]="!commentService.commentPage.pageSize">
|
||||
<ng-template [ngIf]="commentService.commentPage.pageSize">
|
||||
<nz-comment *ngFor="let comment of commentService.commentPage.list; let i = index"
|
||||
[nzAuthor]="comment.authorName"
|
||||
[nzDatetime]="comment.date">
|
||||
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="comment.authorAvatarImgUrl"></nz-avatar>
|
||||
<nz-comment-content class="comment">
|
||||
<p>
|
||||
{{comment.content}}
|
||||
</p>
|
||||
</nz-comment-content>
|
||||
<nz-comment-action><span (click)="replyTo(comment.id,comment.authorName,i)">
|
||||
<i nz-icon nzType="message" nzTheme="fill"></i> 回复</span>
|
||||
</nz-comment-action>
|
||||
|
||||
<!-- 二级评论 -->
|
||||
<ng-container *ngIf="comment.child && comment.child.length">
|
||||
|
||||
<nz-comment *ngFor="let secComment of comment.child" [nzAuthor]="secComment.authorName"
|
||||
[nzDatetime]="secComment.date">
|
||||
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="secComment.authorAvatarImgUrl"></nz-avatar>
|
||||
<nz-comment-content class="comment">
|
||||
<p>
|
||||
{{secComment.content}}
|
||||
</p>
|
||||
</nz-comment-content>
|
||||
<!-- <nz-comment-action><span (click)="replyTo(secComment.id,secComment.authorName,i)">
|
||||
<i nz-icon nzType="message" nzTheme="fill"></i> 回复</span>
|
||||
</nz-comment-action> -->
|
||||
|
||||
</nz-comment>
|
||||
|
||||
</ng-container>
|
||||
<!-- 二级评论的回复框 -->
|
||||
<nz-comment *ngIf="responseComment.pid!=null&&relyIndex==i">
|
||||
<nz-comment>
|
||||
<nz-comment-content>
|
||||
<nz-form-item>
|
||||
<textarea nz-input [(ngModel)]="responseComment.content"
|
||||
style="height: 130px;width: 80%;padding: 10px;border-radius: 5px;"></textarea>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button nzType="primary" [disabled]="!responseComment.content" (click)="reply()"> 回复
|
||||
</button>
|
||||
<button nz-button nzType="default" style="margin-left: 15px;" (click)="responseComment.pid=null"> 取消
|
||||
</button>
|
||||
</nz-form-item>
|
||||
</nz-comment-content>
|
||||
</nz-comment>
|
||||
</nz-comment>
|
||||
</nz-comment>
|
||||
|
||||
<nz-pagination style="text-align: center" [nzPageIndex]="pageNum"
|
||||
[nzTotal]="commentService.commentPage.total"
|
||||
[nzPageSize]="pageSize" [nzHideOnSinglePage]="true"
|
||||
(nzPageIndexChange)="toPage($event)"></nz-pagination>
|
||||
|
||||
</ng-template>
|
||||
</nz-card>
|
||||
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</nz-spin>
|
||||
</ng-template>
|
||||
25
index/src/app/pages/article/article.component.spec.ts
Normal file
25
index/src/app/pages/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();
|
||||
});
|
||||
});
|
||||
228
index/src/app/pages/article/article.component.ts
Normal file
228
index/src/app/pages/article/article.component.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Router} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {Location} from '@angular/common';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {ArticleService} from '../../services/article/article.service';
|
||||
import {CommentService} from '../../services/comment/comment.service';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
import {CommentReq} from '../../class/commentReq';
|
||||
import {Article} from '../../class/article';
|
||||
|
||||
declare var editormd;
|
||||
declare var $;
|
||||
|
||||
@Component({
|
||||
selector: 'app-article',
|
||||
templateUrl: './article.component.html',
|
||||
styleUrls: ['./article.component.css']
|
||||
})
|
||||
export class ArticleComponent implements OnInit {
|
||||
|
||||
constructor(private articleService: ArticleService,
|
||||
private routerinfo: ActivatedRoute,
|
||||
private router: Router,
|
||||
private message: NzMessageService,
|
||||
private titleService: Title,
|
||||
private location: Location,
|
||||
public commentService: CommentService,
|
||||
public userService: UserService) {
|
||||
}
|
||||
|
||||
copyRightUrl: string;
|
||||
|
||||
public pageNum: number = 1;
|
||||
public pageSize: number = 10;
|
||||
|
||||
public articleId: number;
|
||||
|
||||
public article: Article = new Article();
|
||||
|
||||
public comment4submit: string = '';
|
||||
|
||||
loadOk: boolean = false; // 文章的加载
|
||||
|
||||
loading: boolean = true; // 评论的加载
|
||||
|
||||
relyIndex: number;
|
||||
|
||||
public responseComment: CommentReq;
|
||||
|
||||
public showToc: boolean = true;
|
||||
|
||||
/**
|
||||
* editor.md preview
|
||||
*
|
||||
*/
|
||||
public preview(markdownContent) {
|
||||
editormd.markdownToHTML('content', {
|
||||
markdown: markdownContent,
|
||||
// htmlDecode : true, // 开启 HTML 标签解析,为了安全性,默认不开启
|
||||
htmlDecode: 'style,script,iframe', // you can filter tags decode
|
||||
toc : true,
|
||||
tocm : true, // Using [TOCM]
|
||||
tocContainer : '#article-toc', // 自定义 ToC 容器层
|
||||
// gfm : false,
|
||||
tocDropdown: false,
|
||||
// markdownSourceCode : true, // 是否保留 Markdown 源码,即是否删除保存源码的 Textarea 标签
|
||||
emoji: true,
|
||||
taskList: true,
|
||||
// tex : true, //科学公式 默认不解析
|
||||
flowChart: true, // 默认不解析
|
||||
sequenceDiagram: true, // 默认不解析
|
||||
});
|
||||
this.checkALink();
|
||||
}
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
this.articleId = +this.routerinfo.snapshot.paramMap.get('id');
|
||||
this.responseComment = new CommentReq(true);
|
||||
this.getArticle();
|
||||
this.getPageComment();
|
||||
this.responseComment.articleID = this.articleId;
|
||||
this.copyRightUrl = location.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并处理文章
|
||||
*/
|
||||
getArticle() {
|
||||
this.articleService.getArticleById(this.articleId).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
// 清空远先的markdown 内容
|
||||
document.getElementById('content').innerHTML = '';
|
||||
this.article = data.result;
|
||||
this.preview(data.result.mdContent);
|
||||
window.scrollTo(0, 0);
|
||||
this.titleService.setTitle(data.result.title);
|
||||
// 修改url栏的地址
|
||||
this.location.replaceState('article/' + data.result.id);
|
||||
this.copyRightUrl = location.href;
|
||||
this.loadOk = true;
|
||||
} else if (data.code === 201) {
|
||||
// 文章不存在
|
||||
this.router.navigateByUrl('404');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 跳转id文章
|
||||
* @param id 文章id
|
||||
*/
|
||||
toArticle(id: number) {
|
||||
if (id == null || id <= 0) {
|
||||
return;
|
||||
}
|
||||
this.articleId = id;
|
||||
this.loadOk = false;
|
||||
this.getArticle();
|
||||
this.getPageComment();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 评论调到某一页
|
||||
* @param a 页码数
|
||||
*/
|
||||
toPage(a: number) {
|
||||
if (a === this.pageNum) {
|
||||
return;
|
||||
}
|
||||
this.pageNum = a;
|
||||
this.getPageComment();
|
||||
}
|
||||
|
||||
// 评论
|
||||
getPageComment() {
|
||||
this.commentService.getPageComment(this.articleId, this.pageNum, this.pageSize).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.loading = false;
|
||||
this.commentService.getResponseComment();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 一级评论的创建
|
||||
*/
|
||||
submitComment() {
|
||||
if (this.comment4submit == null || this.comment4submit === '') {
|
||||
this.message.info('内容不能为空');
|
||||
return;
|
||||
}
|
||||
const submitBody: CommentReq = new CommentReq(true);
|
||||
submitBody.content = this.comment4submit;
|
||||
submitBody.articleID = this.articleId;
|
||||
submitBody.pid = -1;
|
||||
|
||||
this.commentService.submitComment(submitBody);
|
||||
this.comment4submit = null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文本输入框的过程
|
||||
* @param id 父评论的id
|
||||
* @param name 富评论的作者名
|
||||
* @param index 索引 便于显示输入框
|
||||
*/
|
||||
replyTo(id, name, index) {
|
||||
if (!this.userService.userInfo) {
|
||||
this.message.info('请先登录哟~~~');
|
||||
return;
|
||||
}
|
||||
this.responseComment.pid = id;
|
||||
this.responseComment.content = ' @' + name + ' ';
|
||||
this.relyIndex = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回复的数据提交
|
||||
*/
|
||||
reply() {
|
||||
if (this.responseComment.content == null || this.responseComment.content === '') {
|
||||
this.message.info('内容不能为空');
|
||||
return;
|
||||
}
|
||||
if (this.responseComment.pid == null) {
|
||||
this.message.error('非法操作');
|
||||
}
|
||||
this.commentService.rely(this.responseComment).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
if (this.commentService.commentPage.list[this.relyIndex].child == null) {
|
||||
this.commentService.commentPage.list[this.relyIndex].child = [];
|
||||
}
|
||||
this.commentService.commentPage.list[this.relyIndex].child.unshift(data.result);
|
||||
this.responseComment.content = null;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// 曲线救国 重新设置a的href值
|
||||
checkALink() {
|
||||
const as = document.getElementsByTagName('a');
|
||||
let hrefStr: string = null ;
|
||||
let count: number = 0;
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < as.length; i++) {
|
||||
hrefStr = decodeURI(as[i].href);
|
||||
// console.log(as[i].getAttribute('level'));
|
||||
if (as[i].getAttribute('level') != null) {
|
||||
// 截取锚点名称
|
||||
const anchorName: string = hrefStr.substr(hrefStr.indexOf('#'), hrefStr.length);
|
||||
// 重新获取url 并拼接锚点名称
|
||||
as[i].href = location.origin + location.pathname + anchorName;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count === 0) {
|
||||
this.showToc = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
index/src/app/pages/categories/category.component.css
Normal file
65
index/src/app/pages/categories/category.component.css
Normal file
@@ -0,0 +1,65 @@
|
||||
#main{
|
||||
width:60%;
|
||||
margin:0 auto;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background: #ffffff;
|
||||
}
|
||||
#category{
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ececec;
|
||||
box-shadow: 5px 5px 5px #888888;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#detail{
|
||||
clear: both;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.singleTag{
|
||||
background: #ffffff;
|
||||
border-radius: 5px;
|
||||
line-height: 15px;
|
||||
margin: 5px 10px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
border: 1px solid #000000;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
ul{
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.art{
|
||||
padding: 8px;
|
||||
border: 1px solid #ececec;
|
||||
box-shadow: 5px 5px 5px #aaaaaa;
|
||||
margin: 20px 0 ;
|
||||
}
|
||||
.title:hover{
|
||||
animation: 1s move ;
|
||||
}
|
||||
@keyframes move{
|
||||
from{
|
||||
margin-left: 0;
|
||||
}
|
||||
to{
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.active{
|
||||
background: #6DAB90;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width:768px){
|
||||
|
||||
#main{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
25
index/src/app/pages/categories/category.component.html
Normal file
25
index/src/app/pages/categories/category.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<div id="main">
|
||||
|
||||
<div id="category">
|
||||
<ul *ngIf="categoryService.categories">
|
||||
<li *ngFor="let category of categoryService.categories" (click)="changeCategory(category.name)">
|
||||
<span class="singleTag" [ngClass]="{active:currentCategoryName==category.name}"
|
||||
[nzTitle]="'此分类有'+(category.articles.split(',').length-1)+'篇文章'" nzPlacement="topCenter" nz-tooltip>
|
||||
<i nz-icon nzType="folder" nzTheme="fill"></i> {{category.name}}</span></li>
|
||||
</ul>
|
||||
<h2 *ngIf="!categoryService.categories">暂时没有分类</h2>
|
||||
</div>
|
||||
|
||||
<ul id="detail" *ngIf="currentArticleList">
|
||||
<li *ngFor="let article of currentArticleList.list">
|
||||
<div class="art">
|
||||
<h2><a [routerLink]="'/article/'+article.id" class="title">{{article.title}}</a></h2>
|
||||
<hr>
|
||||
<div> {{article.summary}}</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div *ngIf="!currentArticleList">
|
||||
<h2 style="text-align: center">该分类暂无文章</h2>
|
||||
</div>
|
||||
</div>
|
||||
25
index/src/app/pages/categories/category.component.spec.ts
Normal file
25
index/src/app/pages/categories/category.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CategoriesComponent } from './category.component';
|
||||
|
||||
describe('CategoriesComponent', () => {
|
||||
let component: CategoriesComponent;
|
||||
let fixture: ComponentFixture<CategoriesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CategoriesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CategoriesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
71
index/src/app/pages/categories/category.component.ts
Normal file
71
index/src/app/pages/categories/category.component.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {Location} from '@angular/common';
|
||||
import {CategoryService} from '../../services/category/category.service';
|
||||
import {ArticleService} from '../../services/article/article.service';
|
||||
import {Page} from '../../class/page';
|
||||
import {Article} from '../../class/article';
|
||||
|
||||
@Component({
|
||||
selector: 'app-categories',
|
||||
templateUrl: './category.component.html',
|
||||
styleUrls: ['./category.component.css']
|
||||
})
|
||||
export class CategoryComponent implements OnInit {
|
||||
|
||||
constructor(private routerinfo: ActivatedRoute,
|
||||
private titleService: Title,
|
||||
private location: Location,
|
||||
public categoryService: CategoryService,
|
||||
public articleService: ArticleService) {
|
||||
titleService.setTitle('小海博客|分类');
|
||||
}
|
||||
|
||||
// 当前timeliness展示的分类
|
||||
public currentCategoryName: string;
|
||||
public currentArticleList: Page<Article>;
|
||||
|
||||
ngOnInit() {
|
||||
this.currentCategoryName = this.routerinfo.snapshot.queryParams.name;
|
||||
if (this.categoryService.categories == null) {
|
||||
this.categoryService.getAllCategory().subscribe((data) => {
|
||||
// 有分类数据
|
||||
if (data.code === 0 && data.result.length > 0) {
|
||||
// 是否更具url的参数获取数据
|
||||
if (this.currentCategoryName == null) {
|
||||
this.currentCategoryName = data.result[0].name;
|
||||
}
|
||||
this.getArticle();
|
||||
}
|
||||
});
|
||||
} else if (this.currentCategoryName == null) {
|
||||
this.currentCategoryName = this.categoryService.categories[0].name;
|
||||
}
|
||||
this.getArticle();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 切换 分类
|
||||
* @param name 分类名称
|
||||
*/
|
||||
changeCategory(name: string) {
|
||||
if (this.currentCategoryName === name) {
|
||||
return;
|
||||
}
|
||||
this.currentCategoryName = name;
|
||||
this.getArticle();
|
||||
this.location.replaceState('category', '?name=' + name);
|
||||
}
|
||||
|
||||
|
||||
getArticle() {
|
||||
this.articleService.getArticleByCategory(this.currentCategoryName).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.currentArticleList = data.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#main{
|
||||
width: 60%;
|
||||
height: 50px;
|
||||
margin: 0 auto;
|
||||
padding: 15% 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<div>
|
||||
<div id="main">
|
||||
<nz-alert [nzType]="type" [nzMessage]="message"
|
||||
[nzDescription]="desc" nzShowIcon>
|
||||
</nz-alert>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
58
index/src/app/pages/email-verify/email-verify.component.ts
Normal file
58
index/src/app/pages/email-verify/email-verify.component.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {Router, ActivatedRoute} from '@angular/router';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-email-verify',
|
||||
templateUrl: './email-verify.component.html',
|
||||
styleUrls: ['./email-verify.component.css']
|
||||
})
|
||||
export class EmailVerifyComponent implements OnInit {
|
||||
|
||||
constructor(private titleService: Title,
|
||||
public userService: UserService,
|
||||
private router: Router,
|
||||
public routerinfo: ActivatedRoute) {
|
||||
titleService.setTitle('小海博客|邮箱验证');
|
||||
}
|
||||
|
||||
type: string = 'info';
|
||||
message: string = '正在验证,请稍等';
|
||||
desc: string = '';
|
||||
|
||||
|
||||
private email: string;
|
||||
private verifyId: string;
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
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 = '链接可能被修改了,请重新点击邮箱中的链接,或者重新发送邮件';
|
||||
}
|
||||
const reqBody = {
|
||||
email: this.email,
|
||||
verifyId: this.verifyId
|
||||
};
|
||||
this.userService.emailVerify(reqBody).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.type = 'success';
|
||||
this.message = '验证成功';
|
||||
// this.desc = "5秒后转跳到后台"
|
||||
// setTimeout(() => {
|
||||
// window.location.href = "admin"
|
||||
// }, 5000);
|
||||
} else {
|
||||
this.type = 'error';
|
||||
this.message = data.msg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
180
index/src/app/pages/index/index.component.css
Normal file
180
index/src/app/pages/index/index.component.css
Normal file
@@ -0,0 +1,180 @@
|
||||
hr {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.main button {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
margin-right: 15px;
|
||||
margin-left: 15px;
|
||||
margin-top: 10px;
|
||||
background: none;
|
||||
border: 1px solid #000000;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.contact {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/* info */
|
||||
.main {
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
width: 350px;
|
||||
margin-bottom: 50px;
|
||||
padding-bottom: 10px;
|
||||
word-break: break-all
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.countInfo i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.countInfo li{
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* article */
|
||||
|
||||
.article {
|
||||
min-height: 200px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.aUl {
|
||||
margin: 20px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.aUl li {
|
||||
margin-right: 12px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.aUl li i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.article .aTitle {
|
||||
margin: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/** todo : 动画效果 */
|
||||
.aTitle:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.article .aType {
|
||||
color: #ffffff;
|
||||
font-size: 0.8em;
|
||||
padding: 2px 6px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.aTypeOriginal {
|
||||
background: #5eb95e;
|
||||
}
|
||||
|
||||
.aTypeReprint {
|
||||
background: #F37B1D;
|
||||
}
|
||||
|
||||
.aSummary {
|
||||
width: 98%;
|
||||
padding-top: 10px;
|
||||
font-size: 1.1em;
|
||||
line-height: 2em;
|
||||
color: #666666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 自动换行 */
|
||||
.aTitle, .aSummary {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tags {
|
||||
margin-top: 10px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.about-contact-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#pagination {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.left {
|
||||
width: 380px;
|
||||
height: auto;
|
||||
flex-grow: 0;
|
||||
border-radius: 10px;
|
||||
margin-left: 80px;
|
||||
padding-bottom: 80px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.right {
|
||||
height: auto;
|
||||
max-width: 1000px;
|
||||
flex-grow: 1;
|
||||
margin-left: 80px;
|
||||
margin-right: 80px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
direction: rtl
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media only screen and ( max-width: 768px ) {
|
||||
#bloger {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.article {
|
||||
margin: 0 10px 30px 10px;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 94%;
|
||||
margin-left: 3%;
|
||||
}
|
||||
|
||||
.left {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
115
index/src/app/pages/index/index.component.html
Normal file
115
index/src/app/pages/index/index.component.html
Normal file
@@ -0,0 +1,115 @@
|
||||
<div class="container">
|
||||
|
||||
|
||||
<h2 *ngIf="!articleService.currentPage" style="width: 100%;text-align: center">.暂时还未发布文章.</h2>
|
||||
|
||||
<div class="right" *ngIf="articleService.currentPage">
|
||||
<nz-card class="article am-animation-slide-right" *ngFor="let article of articleService.currentPage.list"
|
||||
[nzLoading]="!articleService.currentPage">
|
||||
|
||||
<a [routerLink]="'/article/'+article.id"><h2 class="aTitle">{{article.title}}</h2></a>
|
||||
<ul class="aUl">
|
||||
<li>
|
||||
<span class="aType" [ngClass]="{'aTypeOriginal': article.original,'aTypeReprint':!article.original}">
|
||||
{{article.original ? "原创" : "转载"}}
|
||||
</span>
|
||||
</li>
|
||||
<li><i nz-icon nzType="calendar" nzTheme="outline"></i>{{article.publishDateFormat}}</li>
|
||||
<li><i nz-icon nzType="user" nzTheme="outline"></i>{{article.authorName}}</li>
|
||||
<li>
|
||||
<i nz-icon nzType="folder" nzTheme="outline"></i>
|
||||
<a [routerLink]="['/category']" [queryParams]="{'name':article.category}">{{article.category}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="aSummary">
|
||||
<p>{{article.summary}}</p>
|
||||
</div>
|
||||
<a [routerLink]="'/article/'+article.id" style="font-size: 1.2em;">
|
||||
阅读全文
|
||||
<i nz-icon nzType="double-right" nzTheme="outline"></i>
|
||||
</a>
|
||||
<hr>
|
||||
<div class="tags">
|
||||
<span *ngFor="let item of article.tags">
|
||||
<i nz-icon nzType="tag" nzTheme="outline"></i>
|
||||
<a [routerLink]="['/tag']" [queryParams]="{name:item}">{{item}}</a>
|
||||
</span>
|
||||
</div>
|
||||
</nz-card>
|
||||
|
||||
<nz-pagination id="pagination" [nzPageIndex]="pageNum" [nzTotal]="articleService.currentPage.total"
|
||||
[nzPageSize]="pageSize"
|
||||
[nzHideOnSinglePage]="true" (nzPageIndexChange)="getPageArticle($event)"></nz-pagination>
|
||||
</div>
|
||||
|
||||
<div class="left">
|
||||
<!-- 关于我 -->
|
||||
<div class="main am-animation-slide-left" id="bloger">
|
||||
<span class="title">关于我</span>
|
||||
<hr>
|
||||
<div style="text-align: center;">
|
||||
<div class="am-center" align="center">
|
||||
<img [src]="imagePath" width="100px" height="100px" alt="logo">
|
||||
<h2>小海</h2>
|
||||
</div>
|
||||
<div class="about-desc">
|
||||
<span>一个爱好瞎捣鼓的技术宅 : )</span>
|
||||
</div>
|
||||
<div class="about-contact">
|
||||
<button class="about-contact-icon" (mouseenter)="showWxImg()" (mouseleave)="reset()">
|
||||
<i nz-icon nzType="wechat" nzTheme="outline"></i>
|
||||
</button>
|
||||
<button class="about-contact-icon" (mouseenter)="showQQImg()" (mouseleave)="reset()">
|
||||
<i nz-icon nzType="qq" nzTheme="outline"></i>
|
||||
</button>
|
||||
<a href="https://github.com/xiaohai2271">
|
||||
<button class="about-contact-icon">
|
||||
<i nz-icon nzType="github" nzTheme="outline"></i>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 标签云 -->
|
||||
<div class="main am-animation-slide-left">
|
||||
<span class="title">标签云</span>
|
||||
<hr>
|
||||
<div style="margin: 10px;">
|
||||
<span *ngIf="!tagService.tagCloudList">暂无标签</span>
|
||||
<div *ngIf="tagService.tagCloudList">
|
||||
<a *ngFor="let item of tagService.tagCloudList" style="max-width: 90%;word-break: break-all;margin: 0 4px;"
|
||||
[routerLink]="['/tag']" [queryParams]="{name:item.name}">
|
||||
<font [size]="item.size+1" [nzTitle]="item.name" nzPlacement="topCenter" nz-tooltip>{{item.name}}</font>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 网站信息 -->
|
||||
<div class="main am-animation-slide-left">
|
||||
<span class="title">网站信息</span>
|
||||
<hr>
|
||||
<div style="margin: 15px;" *ngIf="countService.count">
|
||||
<ul class="countInfo">
|
||||
<li><i nz-icon nzType="file" nzTheme="outline"></i><span>文章总数</span>:{{countService.count.articleCount}} 篇
|
||||
</li>
|
||||
<li><i nz-icon nzType="tags" nzTheme="outline"></i><span>标签总数</span>:{{countService.count.tagCount}} 个
|
||||
</li>
|
||||
<li><i nz-icon nzType="profile" nzTheme="outline"></i><span>留言总数</span>:{{countService.count.leaveMsgCount}} 条
|
||||
</li>
|
||||
<li><i nz-icon nzType="message" nzTheme="outline"></i><span>评论总数</span>:{{countService.count.commentCount}} 条
|
||||
</li>
|
||||
<li><i nz-icon nzType="eye" nzTheme="outline"></i><span>总访客量</span>:{{countService.count.visitorCount}} 条
|
||||
</li>
|
||||
<li><i nz-icon nzType="arrow-up" nzTheme="outline"></i><span>最后更新</span>:<span
|
||||
class="siteUpdateTime">{{webUpdateService.lastestUpdateTime}}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="!countService.count" style="margin-left: 15px;">暂无更多信息</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
25
index/src/app/pages/index/index.component.spec.ts
Normal file
25
index/src/app/pages/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();
|
||||
});
|
||||
});
|
||||
76
index/src/app/pages/index/index.component.ts
Normal file
76
index/src/app/pages/index/index.component.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {Location} from '@angular/common';
|
||||
import {TagService} from '../../services/tag/tag.service';
|
||||
import {CountService} from '../../services/count/count.service';
|
||||
import {ArticleService} from '../../services/article/article.service';
|
||||
import {WebUpdateService} from '../../services/update/web-update.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-index',
|
||||
templateUrl: './index.component.html',
|
||||
styleUrls: ['./index.component.css']
|
||||
})
|
||||
export class IndexComponent implements OnInit {
|
||||
|
||||
constructor(public tagService: TagService,
|
||||
public countService: CountService,
|
||||
public articleService: ArticleService,
|
||||
public webUpdateService: WebUpdateService,
|
||||
private routerinfo: ActivatedRoute, private title: Title,
|
||||
private location: Location) {
|
||||
title.setTitle('小海博客');
|
||||
}
|
||||
|
||||
public pageNum: number;
|
||||
public pageSize: number;
|
||||
|
||||
imagePath: string;
|
||||
|
||||
ngOnInit() {
|
||||
this.pageNum = this.routerinfo.snapshot.queryParams.page;
|
||||
this.pageSize = this.routerinfo.snapshot.queryParams.count;
|
||||
// 数据合法性验证
|
||||
this.pageNum = (this.pageNum == null || this.pageNum < 1) ? 1 : this.pageNum;
|
||||
this.pageSize = (this.pageSize == null || this.pageSize < 1) ? 5 : this.pageSize;
|
||||
// 获取数据
|
||||
|
||||
if (this.tagService.tagCloudList == null) {
|
||||
this.tagService.getTagCloud();
|
||||
}
|
||||
if (this.countService.count == null) {
|
||||
this.countService.getCount();
|
||||
}
|
||||
this.articleService.getArticle(this.pageNum, this.pageSize);
|
||||
if (this.webUpdateService.updateInfoList == null) {
|
||||
this.webUpdateService.getUpdateInfo();
|
||||
}
|
||||
if (!this.webUpdateService.lastestUpdateTime) {
|
||||
this.webUpdateService.getLastestUpdateTime();
|
||||
}
|
||||
|
||||
// 设置imagePath的初始值
|
||||
this.reset();
|
||||
}
|
||||
|
||||
showQQImg() {
|
||||
this.imagePath = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/qq.jpg';
|
||||
}
|
||||
|
||||
showWxImg() {
|
||||
this.imagePath = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/wx.jpg';
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.imagePath = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/logo.png';
|
||||
}
|
||||
|
||||
getPageArticle(e: number) {
|
||||
this.pageNum = e;
|
||||
// 修改地址栏
|
||||
this.location.replaceState('', '?page=' + e + '&count=' + this.pageSize);
|
||||
this.articleService.getArticle(this.pageNum, this.pageSize);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
53
index/src/app/pages/leave-msg/leave-msg.component.css
Normal file
53
index/src/app/pages/leave-msg/leave-msg.component.css
Normal file
@@ -0,0 +1,53 @@
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.leaveMsg-input {
|
||||
width: 60%;
|
||||
margin-left: 20%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.leaveMsg-input textarea {
|
||||
padding: 10px;
|
||||
max-height: 400px;
|
||||
min-height: 60px;
|
||||
width: 500px;
|
||||
height: 100px;
|
||||
border: 1px solid #4398ED;
|
||||
border-radius: 10px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#leaveMsg {
|
||||
width: 70%;
|
||||
height: 80%;
|
||||
margin-top: 45px;
|
||||
margin-left: 15%;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.comment {
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
#leaveMsgs {
|
||||
width: 60%;
|
||||
margin: 15px auto
|
||||
}
|
||||
|
||||
@media only screen and ( max-width:768px) {
|
||||
#leaveMsgs {
|
||||
width: 96%;
|
||||
margin-left: 2%;
|
||||
}
|
||||
#input-area {
|
||||
width: 80%;
|
||||
margin-left: 10%;
|
||||
}
|
||||
}
|
||||
82
index/src/app/pages/leave-msg/leave-msg.component.html
Normal file
82
index/src/app/pages/leave-msg/leave-msg.component.html
Normal file
@@ -0,0 +1,82 @@
|
||||
<div>
|
||||
|
||||
<span *ngIf="!userService.userInfo" style="display: block;text-align: center">
|
||||
若要评论,请先<a style="color: blue !important;" (click)="userService.showModal('login')">登录</a>哟~~~~
|
||||
</span>
|
||||
<nz-comment *ngIf="userService.userInfo" style="width: 60%;margin:15px auto">
|
||||
<nz-comment-content>
|
||||
<nz-form-item>
|
||||
<textarea [(ngModel)]="content" nz-input style="height: 130px;width: 80%;"></textarea>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button nzType="primary" [disabled]="!content" (click)="submitComment()">
|
||||
评论
|
||||
</button>
|
||||
</nz-form-item>
|
||||
</nz-comment-content>
|
||||
</nz-comment>
|
||||
|
||||
|
||||
<!-- 展示 -->
|
||||
<nz-card id="leaveMsgs" [nzLoading]="!leaveMsgService.leaveMsgPage">
|
||||
<nz-comment *ngFor="let leaveMsg of leaveMsgService.leaveMsgPage.list; let i = index"
|
||||
[nzAuthor]="leaveMsg.authorName"
|
||||
[nzDatetime]="leaveMsg.date">
|
||||
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="leaveMsg.authorAvatarImgUrl"></nz-avatar>
|
||||
<nz-comment-content class="comment">
|
||||
<p>
|
||||
{{leaveMsg.content}}
|
||||
</p>
|
||||
</nz-comment-content>
|
||||
<nz-comment-action><span (click)="replyTo(leaveMsg.id,leaveMsg.authorName,i)">
|
||||
<i nz-icon nzType="message" nzTheme="fill"></i> 回复</span>
|
||||
</nz-comment-action>
|
||||
|
||||
<!-- 二级评论 -->
|
||||
<ng-container *ngIf="leaveMsg.child && leaveMsg.child.length">
|
||||
|
||||
<nz-comment *ngFor="let secLeaveMsg of leaveMsg.child" [nzAuthor]="secLeaveMsg.authorName"
|
||||
[nzDatetime]="secLeaveMsg.date">
|
||||
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="secLeaveMsg.authorAvatarImgUrl"></nz-avatar>
|
||||
<nz-comment-content class="comment">
|
||||
<p>
|
||||
{{secLeaveMsg.content}}
|
||||
</p>
|
||||
</nz-comment-content>
|
||||
<!-- <nz-comment-action><span (click)="replyTo(secLeaveMsg.id,secLeaveMsg.authorName,i)">
|
||||
<i nz-icon nzType="message" nzTheme="fill"></i> 回复</span>
|
||||
</nz-comment-action> -->
|
||||
|
||||
</nz-comment>
|
||||
|
||||
</ng-container>
|
||||
<!-- 二级评论的回复框 -->
|
||||
<nz-comment *ngIf="responseComment.pid!=null&&relyIndex==i">
|
||||
<nz-comment>
|
||||
<nz-comment-content>
|
||||
<nz-form-item>
|
||||
<textarea [(ngModel)]="responseComment.content" nz-input
|
||||
style="height: 130px;width: 80%;padding: 10px;border-radius: 5px;"></textarea>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button nzType="primary" [disabled]="!responseComment.content"
|
||||
(click)="reply()"> 回复
|
||||
</button>
|
||||
<button nz-button nzType="default" style="margin-left: 15px;" (click)="responseComment.pid=null"> 取消
|
||||
</button>
|
||||
</nz-form-item>
|
||||
</nz-comment-content>
|
||||
</nz-comment>
|
||||
</nz-comment>
|
||||
</nz-comment>
|
||||
|
||||
|
||||
<nz-pagination style="text-align: center" [nzPageIndex]="pageNum" [nzTotal]="leaveMsgService.leaveMsgPage.total"
|
||||
[nzPageSize]="pageSize"
|
||||
[nzHideOnSinglePage]="true" (nzPageIndexChange)="toPage($event)"></nz-pagination>
|
||||
|
||||
|
||||
</nz-card>
|
||||
|
||||
|
||||
</div>
|
||||
25
index/src/app/pages/leave-msg/leave-msg.component.spec.ts
Normal file
25
index/src/app/pages/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();
|
||||
});
|
||||
});
|
||||
107
index/src/app/pages/leave-msg/leave-msg.component.ts
Normal file
107
index/src/app/pages/leave-msg/leave-msg.component.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {CommentService} from '../../services/comment/comment.service';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
import {CommentReq} from '../../class/commentReq';
|
||||
|
||||
@Component({
|
||||
selector: 'app-leave-msg',
|
||||
templateUrl: './leave-msg.component.html',
|
||||
styleUrls: ['./leave-msg.component.css']
|
||||
})
|
||||
export class LeaveMsgComponent implements OnInit {
|
||||
|
||||
constructor(public leaveMsgService: CommentService,
|
||||
private message: NzMessageService,
|
||||
public userService: UserService,
|
||||
private titleService: Title) {
|
||||
titleService.setTitle('小海博客|留言');
|
||||
// todo ::: @ {pid} 的name
|
||||
}
|
||||
|
||||
pageNum: number = 1;
|
||||
|
||||
pageSize: number = 10;
|
||||
|
||||
content: string = null;
|
||||
|
||||
// 当前的回复框的所处位置
|
||||
relyIndex: number;
|
||||
|
||||
public responseComment: CommentReq = new CommentReq(false);
|
||||
|
||||
ngOnInit() {
|
||||
this.getPageLeaveMsg();
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
getPageLeaveMsg() {
|
||||
this.leaveMsgService.getLeaveMsg(this.pageNum, this.pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击回复 显示输入框 及准备数据
|
||||
* @param id 父级评论的id
|
||||
* @param name 父级评论者的name
|
||||
* @param index 索引
|
||||
*/
|
||||
replyTo(id, name, index) {
|
||||
if (this.userService.userInfo == null) {
|
||||
this.message.info('请先登录哟~~~');
|
||||
return;
|
||||
}
|
||||
this.responseComment.pid = id;
|
||||
this.responseComment.content = ' @' + name + ' ';
|
||||
this.relyIndex = index;
|
||||
}
|
||||
|
||||
|
||||
toPage(a: number) {
|
||||
if (a === this.pageNum) {
|
||||
return;
|
||||
}
|
||||
this.pageNum = a;
|
||||
this.getPageLeaveMsg();
|
||||
}
|
||||
|
||||
/**
|
||||
* 留言
|
||||
*/
|
||||
submitComment() {
|
||||
if (this.content == null || this.content === '') {
|
||||
this.message.info('内容不能为空');
|
||||
return;
|
||||
}
|
||||
const submitBody: CommentReq = new CommentReq(false);
|
||||
submitBody.content = this.content;
|
||||
this.leaveMsgService.submitComment(submitBody);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 回复的数据提交
|
||||
*/
|
||||
reply() {
|
||||
if (this.responseComment.content == null || this.responseComment.content === '') {
|
||||
this.message.info('内容不能为空');
|
||||
return;
|
||||
}
|
||||
if (this.responseComment.pid == null) {
|
||||
this.message.error('非法操作');
|
||||
}
|
||||
this.responseComment.articleID = -1;
|
||||
this.leaveMsgService.rely(this.responseComment).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
if (this.leaveMsgService.leaveMsgPage.list[this.relyIndex].child == null) {
|
||||
this.leaveMsgService.leaveMsgPage.list[this.relyIndex].child = [];
|
||||
}
|
||||
this.leaveMsgService.leaveMsgPage.list[this.relyIndex].child.unshift(data.result);
|
||||
this.responseComment.content = null;
|
||||
this.responseComment.pid = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
51
index/src/app/pages/login/login.component.css
Normal file
51
index/src/app/pages/login/login.component.css
Normal file
@@ -0,0 +1,51 @@
|
||||
.main {
|
||||
}
|
||||
|
||||
input {
|
||||
margin-bottom: 20px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 500px;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateX(-50%)
|
||||
}
|
||||
|
||||
#email, #password {
|
||||
width: 100%;
|
||||
border: 1px solid #999999;
|
||||
border-radius: 5px;
|
||||
height: 32px;
|
||||
background: #ffffff
|
||||
}
|
||||
|
||||
.br-text, .forget {
|
||||
clear: both;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: none;
|
||||
padding: 4px 6px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.login {
|
||||
background: #0E90D2;
|
||||
color: white
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.main-content {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
34
index/src/app/pages/login/login.component.html
Normal file
34
index/src/app/pages/login/login.component.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<div class="main">
|
||||
<div class="main-content">
|
||||
<form>
|
||||
<h1 style="text-align: center"><strong>小海</strong></h1>
|
||||
<label for="email">邮箱:</label>
|
||||
<br>
|
||||
<input type="email" name="email" id="email" value="" [(ngModel)]="submitBody.email">
|
||||
<br>
|
||||
<label for="password">密码:</label>
|
||||
<br>
|
||||
<input type="password" name="password" id="password" value="" [(ngModel)]="submitBody.password">
|
||||
<br>
|
||||
<label for="remember-me">
|
||||
<input id="remember-me" type="checkbox" name="isRemember" [(ngModel)]="submitBody.isRememberMe">
|
||||
记住密码
|
||||
</label>
|
||||
<div class="br-text">
|
||||
<p>
|
||||
<span>还没有账号?</span>
|
||||
<a style="color: blue" (click)="userService.showModal('registration')">注册</a>
|
||||
</p>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<button type="submit" (click)="doLogin()" class="btn login">登 录</button>
|
||||
<button type="button" class="btn forget" (click)="showForgetPwd=!showForgetPwd">忘记密码 ^_^?</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nz-modal [(nzVisible)]="showForgetPwd" nzTitle="重置密码" (nzOnCancel)="handleCancel()" (nzOnOk)="handleOk()">
|
||||
<input type="email" nz-input placeholder="注册的邮箱" [(ngModel)]="email4Forgot"/>
|
||||
</nz-modal>
|
||||
25
index/src/app/pages/login/login.component.spec.ts
Normal file
25
index/src/app/pages/login/login.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
94
index/src/app/pages/login/login.component.ts
Normal file
94
index/src/app/pages/login/login.component.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Router} from '@angular/router';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {LoginReq} from '../../class/loginReq';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.css']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
constructor(public userService: UserService,
|
||||
private routerinfo: ActivatedRoute,
|
||||
private router: Router,
|
||||
private message: NzMessageService) {
|
||||
}
|
||||
|
||||
public submitBody: LoginReq = new LoginReq();
|
||||
|
||||
|
||||
showForgetPwd: boolean = false;
|
||||
|
||||
email4Forgot: string;
|
||||
|
||||
ngOnInit() {
|
||||
window.scrollTo(0, 0);
|
||||
if (this.userService.tempUser) {
|
||||
this.submitBody.email = this.userService.tempUser.email;
|
||||
this.submitBody.password = this.userService.tempUser.password;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 登录
|
||||
doLogin() {
|
||||
if (this.submitBody.email == null || this.submitBody.email === '') {
|
||||
this.message.warning('邮箱不能为空');
|
||||
return;
|
||||
}
|
||||
if (this.submitBody.password == null || this.submitBody.password === '') {
|
||||
this.message.warning('密码不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
const regExp = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
||||
if (!regExp.test(this.submitBody.email)) {
|
||||
this.message.error('邮箱格式不合法');
|
||||
return;
|
||||
}
|
||||
|
||||
this.userService.login(this.submitBody).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
// 登录成功
|
||||
// 置空
|
||||
this.userService.tempUser = null;
|
||||
this.message.success('登录成功,欢迎你' + data.result.displayName);
|
||||
} else {
|
||||
// 登录失败
|
||||
this.message.error('登录失败,原因:' + data.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
handleCancel() {
|
||||
this.showForgetPwd = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发生重置密码的邮件
|
||||
*/
|
||||
handleOk() {
|
||||
if (this.email4Forgot == null) {
|
||||
return;
|
||||
}
|
||||
const regExp = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
||||
if (!regExp.test(this.email4Forgot)) {
|
||||
this.message.error('邮箱格式不合法');
|
||||
return;
|
||||
}
|
||||
this.showForgetPwd = false;
|
||||
this.userService.sendResetPwdEmail(this.email4Forgot).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.message.success('发送成功,请前往邮箱进行操作');
|
||||
} else {
|
||||
this.message.error(data.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
87
index/src/app/pages/not-found/not-found.component.css
Normal file
87
index/src/app/pages/not-found/not-found.component.css
Normal file
@@ -0,0 +1,87 @@
|
||||
div,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
img,
|
||||
strong,
|
||||
b,
|
||||
i,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
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: 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;
|
||||
}
|
||||
}
|
||||
6
index/src/app/pages/not-found/not-found.component.html
Normal file
6
index/src/app/pages/not-found/not-found.component.html
Normal file
@@ -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>
|
||||
25
index/src/app/pages/not-found/not-found.component.spec.ts
Normal file
25
index/src/app/pages/not-found/not-found.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotFoundComponent } from './not-found.component';
|
||||
|
||||
describe('NotFoundComponent', () => {
|
||||
let component: NotFoundComponent;
|
||||
let fixture: ComponentFixture<NotFoundComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ NotFoundComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NotFoundComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
28
index/src/app/pages/not-found/not-found.component.ts
Normal file
28
index/src/app/pages/not-found/not-found.component.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Router, NavigationError} from '@angular/router';
|
||||
import {Location} from '@angular/common';
|
||||
|
||||
import {filter} from 'rxjs/operators';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-not-found',
|
||||
templateUrl: './not-found.component.html',
|
||||
styleUrls: ['./not-found.component.css']
|
||||
})
|
||||
export class NotFoundComponent implements OnInit {
|
||||
|
||||
constructor(router: Router, location: Location, private titleService: Title) {
|
||||
titleService.setTitle('小海博客|404');
|
||||
router.events.pipe(
|
||||
filter(event => event instanceof NavigationError)
|
||||
).subscribe((event: NavigationError) => {
|
||||
router.navigate(['/404'], {skipLocationChange: true})
|
||||
.then(() => location.go(event.url));
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
101
index/src/app/pages/partner-sites/partner-sites.component.css
Normal file
101
index/src/app/pages/partner-sites/partner-sites.component.css
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
i {
|
||||
margin-right: 10px;
|
||||
color: royalblue;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #909090
|
||||
}
|
||||
|
||||
.site-middle {
|
||||
width: 60%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.partner-sites {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.partner-sites 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;
|
||||
}
|
||||
|
||||
.placard-content 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;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: none;
|
||||
padding: 5px 8px;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
background: #aaa4a4
|
||||
}
|
||||
|
||||
.submit {
|
||||
background: #2799FF
|
||||
}
|
||||
|
||||
@media only screen and ( max-width: 768px ) {
|
||||
.site-middle, .placard {
|
||||
margin-left: 2px;
|
||||
width: 96%;
|
||||
}
|
||||
|
||||
.partner-sites li {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<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 linkService.Links">
|
||||
<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>网站名称:</label>
|
||||
<input class="contentinput" placeholder="请输入网站名称" [(ngModel)]="link.name">
|
||||
<br>
|
||||
<label>网站链接:</label>
|
||||
<input class="contentinput" placeholder="请输入网站链接" [(ngModel)]="link.url">
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #modalFooter>
|
||||
<button class="btn cancel" (click)="cancel()">取消</button>
|
||||
<button class="btn submit" (click)="apply()">提交</button>
|
||||
</ng-template>
|
||||
</nz-modal>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PartnerSitesComponent } from './partner-sites.component';
|
||||
|
||||
describe('PartnerSitesComponent', () => {
|
||||
let component: PartnerSitesComponent;
|
||||
let fixture: ComponentFixture<PartnerSitesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PartnerSitesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PartnerSitesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
65
index/src/app/pages/partner-sites/partner-sites.component.ts
Normal file
65
index/src/app/pages/partner-sites/partner-sites.component.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {LinkService} from '../../services/link/link.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-partner-sites',
|
||||
templateUrl: './partner-sites.component.html',
|
||||
styleUrls: ['./partner-sites.component.css']
|
||||
})
|
||||
export class PartnerSitesComponent implements OnInit {
|
||||
|
||||
constructor(public linkService: LinkService,
|
||||
private message: NzMessageService,
|
||||
private titleService: Title) {
|
||||
titleService.setTitle('小海博客|友链');
|
||||
}
|
||||
|
||||
showModal: boolean = false;
|
||||
|
||||
// 申请时的链接
|
||||
link = {
|
||||
name: '',
|
||||
url: ''
|
||||
};
|
||||
|
||||
ngOnInit() {
|
||||
window.scrollTo(0, 0);
|
||||
if (this.linkService.Links == null) {
|
||||
this.linkService.getLinks();
|
||||
}
|
||||
}
|
||||
|
||||
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.linkService.apply(this.link).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.message.success('提交成功,请稍等,即将为你处理');
|
||||
} else {
|
||||
this.message.error('提交失败,原因:' + data.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.showModal = false;
|
||||
this.link.name = null;
|
||||
this.link.url = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
93
index/src/app/pages/registration/registration.component.css
Normal file
93
index/src/app/pages/registration/registration.component.css
Normal file
@@ -0,0 +1,93 @@
|
||||
.main {
|
||||
width: 500px;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.ioc_text {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.remain {
|
||||
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin: 20px 0 0 12%;
|
||||
height: 32px;
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
width: 75%;
|
||||
height: 32px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.verStatus {
|
||||
width: 10px;
|
||||
line-height: 32px;
|
||||
margin-left: -20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.br-text {
|
||||
margin-left: 12%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 80%;
|
||||
height: 40px;
|
||||
margin-left: 10%;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: turquoise;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3385FF
|
||||
}
|
||||
|
||||
|
||||
/* 全屏遮罩 */
|
||||
.fullS {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background: none;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.loading span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.main {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
56
index/src/app/pages/registration/registration.component.html
Normal file
56
index/src/app/pages/registration/registration.component.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<div>
|
||||
<div class="main">
|
||||
<div class="ioc_text">
|
||||
<h1><strong>小海</strong></h1>
|
||||
</div>
|
||||
<form>
|
||||
<div class="remain">
|
||||
<div class="input-group mb-4 bootint" id="username">
|
||||
<!-- <div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fa fa-user"></i></span>
|
||||
</div> -->
|
||||
<input type="text" name="email" class="form-control" placeholder="请输入你的邮箱" [(ngModel)]="email">
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-4 bootint" id="password">
|
||||
<!-- <div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fa fa-unlock-alt"></i></span>
|
||||
</div> -->
|
||||
<input type="password" name="password" class="form-control" placeholder="请输入你要设置的密码(8位以上)"
|
||||
[(ngModel)]="password">
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-4 bootint" id="repassword">
|
||||
<input type="password" name="repaassword" class="form-control" placeholder="请再次输入密码" [(ngModel)]="rePassword">
|
||||
<i nz-icon class="verStatus" [hidden]="rePassword==null" [nzType]="password==rePassword?'check-circle':'close-circle'" nzTheme="outline"></i>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" name="code" placeholder="验证码" (blur)="handleKeyUp()" [(ngModel)]="imgCode"
|
||||
class="form-control" style="width: 60%;" id="verifyImgCode">
|
||||
<i nz-icon class="verStatus" [hidden]="imgCode==''" [nzType]="imgCodeStatus?'check-circle':'close-circle'" nzTheme="outline"></i>
|
||||
<img [src]="imgCodeUrl" (click)="changeImg()" alt="Code" title="点击更换验证码"/>
|
||||
</div>
|
||||
|
||||
<div class="br-text">
|
||||
<p>
|
||||
<span>已有账号了?</span>
|
||||
<a (click)="userService.showModal('login')">登录</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="padding-top: 10px">
|
||||
<button type="submit" class="btn" (click)="doRegistration()">注册</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fullS" *ngIf="show">
|
||||
<nz-spin [nzSpinning]="show" class="loading">
|
||||
<div class="loading">
|
||||
<span>加载中..</span>
|
||||
</div>
|
||||
</nz-spin>
|
||||
</div>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
92
index/src/app/pages/registration/registration.component.ts
Normal file
92
index/src/app/pages/registration/registration.component.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
import {environment} from '../../../environments/environment';
|
||||
import {LoginReq} from '../../class/loginReq';
|
||||
|
||||
@Component({
|
||||
selector: 'app-registration',
|
||||
templateUrl: './registration.component.html',
|
||||
styleUrls: ['./registration.component.css']
|
||||
})
|
||||
export class RegistrationComponent implements OnInit {
|
||||
|
||||
|
||||
imgCodeUrl: string = environment.host + '/imgCode';
|
||||
|
||||
// 遮罩
|
||||
show = false;
|
||||
// 输入框的验证码
|
||||
imgCode: string = '';
|
||||
email: string = null;
|
||||
password: string = null;
|
||||
rePassword: string = null;
|
||||
// 验证码验证状态
|
||||
public imgCodeStatus: boolean;
|
||||
|
||||
constructor(public userService: UserService,
|
||||
private router: Router,
|
||||
private message: NzMessageService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
changeImg() {
|
||||
this.imgCodeUrl = environment.host + '/imgCode?time=' + (new Date()).getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码验证
|
||||
*/
|
||||
handleKeyUp() {
|
||||
if (this.imgCode.length === 4) {
|
||||
this.userService.imgCodeVerify(this.imgCode).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.imgCodeStatus = true;
|
||||
} else {
|
||||
this.imgCodeStatus = false;
|
||||
this.message.warning('验证码验证失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册的数据提交
|
||||
*/
|
||||
doRegistration() {
|
||||
if (this.imgCodeStatus) {
|
||||
if (this.email == null || this.password == null || this.rePassword == null) {
|
||||
this.message.warning('用户名和密码均不能为空');
|
||||
return;
|
||||
}
|
||||
if (this.rePassword !== this.password) {
|
||||
this.message.warning('两次密码不匹配');
|
||||
return;
|
||||
}
|
||||
this.show = true;
|
||||
|
||||
this.userService.registration(this.email, this.password).subscribe(data => {
|
||||
this.show = false;
|
||||
this.userService.tempUser = new LoginReq();
|
||||
if (data.code === 0) {
|
||||
this.userService.tempUser.email = this.email;
|
||||
this.userService.tempUser.password = this.password;
|
||||
// 注册成功
|
||||
this.message.success('注册成功!');
|
||||
setTimeout(() => {
|
||||
// 换成登录的modal
|
||||
this.userService.loginModalType = 'login';
|
||||
}, 300);
|
||||
} else {
|
||||
this.message.error('注册失败,原因:' + data.msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
document.getElementById('verifyImgCode').focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
index/src/app/pages/reset-pwd/reset-pwd.component.css
Normal file
27
index/src/app/pages/reset-pwd/reset-pwd.component.css
Normal file
@@ -0,0 +1,27 @@
|
||||
#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
|
||||
}
|
||||
13
index/src/app/pages/reset-pwd/reset-pwd.component.html
Normal file
13
index/src/app/pages/reset-pwd/reset-pwd.component.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<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>
|
||||
25
index/src/app/pages/reset-pwd/reset-pwd.component.spec.ts
Normal file
25
index/src/app/pages/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();
|
||||
});
|
||||
});
|
||||
71
index/src/app/pages/reset-pwd/reset-pwd.component.ts
Normal file
71
index/src/app/pages/reset-pwd/reset-pwd.component.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {Router, ActivatedRoute} from '@angular/router';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reset-pwd',
|
||||
templateUrl: './reset-pwd.component.html',
|
||||
styleUrls: ['./reset-pwd.component.css']
|
||||
})
|
||||
export class ResetPwdComponent implements OnInit {
|
||||
|
||||
constructor(private message: NzMessageService,
|
||||
public userService: UserService,
|
||||
private router: Router,
|
||||
private routerinfo: ActivatedRoute) {
|
||||
}
|
||||
|
||||
private pwd: string;
|
||||
private rePwd: string;
|
||||
|
||||
private email: string;
|
||||
private verifyId: string;
|
||||
|
||||
iserror: boolean = false;
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
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('两次密码不一致');
|
||||
}
|
||||
|
||||
const reqBody = {
|
||||
email: this.email,
|
||||
verifyId: this.verifyId,
|
||||
pwd: this.pwd
|
||||
};
|
||||
this.userService.resetPWd(reqBody).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.message.success('重置密码成功,5秒后转跳到登录页面。');
|
||||
setTimeout(() => {
|
||||
this.router.navigateByUrl('/login');
|
||||
// window.location.href = '/login';
|
||||
}, 5000);
|
||||
} else {
|
||||
this.message.error(data.msg);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
65
index/src/app/pages/tag/tag.component.css
Normal file
65
index/src/app/pages/tag/tag.component.css
Normal file
@@ -0,0 +1,65 @@
|
||||
#main{
|
||||
width:60%;
|
||||
margin:0 auto;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background: #ffffff;
|
||||
}
|
||||
#tag{
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ececec;
|
||||
box-shadow: 5px 5px 5px #888888;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#detail{
|
||||
clear: both;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.singleTag{
|
||||
background: #ffffff;
|
||||
border-radius: 5px;
|
||||
line-height: 15px;
|
||||
margin: 5px 10px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
border: 1px solid #000000;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
ul{
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.art{
|
||||
padding: 8px;
|
||||
border: 1px solid #ececec;
|
||||
box-shadow: 5px 5px 5px #aaaaaa;
|
||||
margin: 20px 0 ;
|
||||
}
|
||||
.title:hover{
|
||||
animation: 1s move ;
|
||||
}
|
||||
@keyframes move{
|
||||
from{
|
||||
margin-left: 0;
|
||||
}
|
||||
to{
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.active{
|
||||
background: #6DAB90;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:768px){
|
||||
|
||||
#main{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
25
index/src/app/pages/tag/tag.component.html
Normal file
25
index/src/app/pages/tag/tag.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<div id="main">
|
||||
|
||||
<div id="tag">
|
||||
<ul>
|
||||
<li *ngFor="let tag of tagService.tagCloudList" (click)="changeTag(tag.name)">
|
||||
<span class="singleTag" [ngClass]="{active: currentTagName==tag.name}" [nzTitle]="'此标签有'+tag.size+'篇文章'"
|
||||
nzPlacement="topCenter" nz-tooltip>
|
||||
<i nz-icon nzType="tag" nzTheme="fill"></i> {{tag.name}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul id="detail" *ngIf="curremtArticlePage">
|
||||
<li *ngFor="let article of curremtArticlePage.list">
|
||||
<div class="art">
|
||||
<h2><a [routerLink]="'/article/'+article.id" class="title">{{article.title}}</a></h2>
|
||||
<hr>
|
||||
<div> {{article.summary}}</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div *ngIf="!curremtArticlePage">
|
||||
<h2 style="text-align: center">该分类暂无文章</h2>
|
||||
</div>
|
||||
</div>
|
||||
25
index/src/app/pages/tag/tag.component.spec.ts
Normal file
25
index/src/app/pages/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();
|
||||
});
|
||||
});
|
||||
62
index/src/app/pages/tag/tag.component.ts
Normal file
62
index/src/app/pages/tag/tag.component.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {Router, ActivatedRoute} from '@angular/router';
|
||||
import {Location} from '@angular/common';
|
||||
import {TagService} from '../../services/tag/tag.service';
|
||||
import {ArticleService} from '../../services/article/article.service';
|
||||
import {Page} from '../../class/page';
|
||||
import {Article} from '../../class/article';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tags',
|
||||
templateUrl: './tag.component.html',
|
||||
styleUrls: ['./tag.component.css']
|
||||
})
|
||||
|
||||
export class TagComponent implements OnInit {
|
||||
|
||||
constructor(private titleService: Title,
|
||||
public tagService: TagService,
|
||||
public articleService: ArticleService,
|
||||
private router: Router,
|
||||
private routerinfo: ActivatedRoute,
|
||||
private location: Location) {
|
||||
titleService.setTitle('小海博客|标签');
|
||||
}
|
||||
|
||||
public currentTagName: string;
|
||||
public curremtArticlePage: Page<Article>;
|
||||
|
||||
ngOnInit() {
|
||||
window.scrollTo(0, 0);
|
||||
this.currentTagName = this.routerinfo.snapshot.queryParams.name;
|
||||
|
||||
if (this.tagService.tagCloudList == null) {
|
||||
this.tagService.getTagCloud().subscribe((data: any) => {
|
||||
if (data.code === 0) {
|
||||
// 根据当前获取到的tag name获取数据
|
||||
if (this.currentTagName == null) {
|
||||
this.currentTagName = data.result[0].name;
|
||||
}
|
||||
this.getArticle();
|
||||
}
|
||||
});
|
||||
} else if (this.currentTagName == null) {
|
||||
this.currentTagName = this.tagService.tagCloudList[0].name;
|
||||
}
|
||||
this.getArticle();
|
||||
}
|
||||
|
||||
|
||||
changeTag(name) {
|
||||
this.currentTagName = name;
|
||||
this.location.replaceState('tag', '?name=' + name);
|
||||
this.getArticle();
|
||||
}
|
||||
|
||||
getArticle() {
|
||||
this.articleService.getArticleByTag(this.currentTagName).subscribe(data => {
|
||||
this.curremtArticlePage = data.result;
|
||||
});
|
||||
}
|
||||
}
|
||||
96
index/src/app/pages/update/update.component.css
Normal file
96
index/src/app/pages/update/update.component.css
Normal file
@@ -0,0 +1,96 @@
|
||||
@media screen and (min-width: 768px) {
|
||||
.site-inner{
|
||||
margin: 0 19%;
|
||||
}
|
||||
.word p{
|
||||
text-indent: 7em;
|
||||
}
|
||||
.word{
|
||||
padding: 20px 25px;
|
||||
}
|
||||
.zh-update{
|
||||
margin: 0 25%;
|
||||
}
|
||||
.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: 30px auto;
|
||||
}
|
||||
.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.5rem;
|
||||
}
|
||||
56
index/src/app/pages/update/update.component.html
Normal file
56
index/src/app/pages/update/update.component.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<div id="app">
|
||||
<!--页面主体-->
|
||||
<div id="main">
|
||||
<div class="container">
|
||||
|
||||
|
||||
<div class="site-inner">
|
||||
|
||||
<div class="site-inner-img">
|
||||
|
||||
<img src="https://zhy-myblog.oss-cn-shenzhen.aliyuncs.com/static/img/sun.jpg" class="am-img-thumbnail">
|
||||
</div>
|
||||
|
||||
<div class="site-inner-mywords">
|
||||
<br>
|
||||
<div class="word">
|
||||
过自己喜欢的生活,<br><br>
|
||||
<p>做自己喜欢的人</p><br>
|
||||
<p>不以物喜</p>
|
||||
<p>不以己悲</p>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="zh-update">
|
||||
<div class="zh-update-log">
|
||||
<div class="zh-update-log-title">
|
||||
<h1>更新日志</h1>
|
||||
<p>最后更新于 {{updateService.lastestUpdateTime}}</p>
|
||||
</div>
|
||||
<div class="zh-update-log-content">
|
||||
|
||||
|
||||
<div class="update-log am-animation-slide-bottom" *ngFor="let update of updateService.updateInfoList">
|
||||
<div class="update-log-date">
|
||||
<i nz-icon nzType="star" nzTheme="twotone" nzTwotoneColor="#eb2f96" class="titleTag"></i>
|
||||
<h2>{{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>
|
||||
25
index/src/app/pages/update/update.component.spec.ts
Normal file
25
index/src/app/pages/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();
|
||||
});
|
||||
});
|
||||
28
index/src/app/pages/update/update.component.ts
Normal file
28
index/src/app/pages/update/update.component.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {WebUpdateService} from '../../services/update/web-update.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-update',
|
||||
templateUrl: './update.component.html',
|
||||
styleUrls: ['./update.component.css']
|
||||
})
|
||||
export class UpdateComponent implements OnInit {
|
||||
|
||||
constructor(public updateService: WebUpdateService,
|
||||
private titleService: Title) {
|
||||
titleService.setTitle('小海博客|更新');
|
||||
}
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
if (this.updateService.updateInfoList == null) {
|
||||
this.updateService.getUpdateInfo();
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
if (!this.updateService.lastestUpdateTime) {
|
||||
this.updateService.getLastestUpdateTime();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
30
index/src/app/pages/write/editor/editor-md.directive.ts
Normal file
30
index/src/app/pages/write/editor/editor-md.directive.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {AfterViewInit, Attribute, Directive, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {EditorConfig} from '../../../class/editor-config';
|
||||
|
||||
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());
|
||||
});
|
||||
}
|
||||
}
|
||||
133
index/src/app/pages/write/write.component.css
Normal file
133
index/src/app/pages/write/write.component.css
Normal file
@@ -0,0 +1,133 @@
|
||||
/* @import '../../../../../assets/editormd/editor.css';
|
||||
|
||||
.fa {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
h2 {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.con {
|
||||
display: flex;
|
||||
flex-wrap: wrap
|
||||
}
|
||||
|
||||
#title {
|
||||
/* 85% */
|
||||
flex-grow: 1;
|
||||
margin: 55px 0 5px 3%;
|
||||
height: 35px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
#submit {
|
||||
margin: 55px 3% 5px 20px;
|
||||
width: 100px;
|
||||
height: 35px;
|
||||
background: #D83731;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: white
|
||||
}
|
||||
|
||||
#md {
|
||||
min-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/*发布博客模态框设置*/
|
||||
|
||||
.am-modal-bd {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.article-tag,
|
||||
.article-type,
|
||||
.articleUrlHide {
|
||||
padding: 10px 20px 10px;
|
||||
}
|
||||
|
||||
.row {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.publish-tag {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tags,
|
||||
.type,
|
||||
.tag,
|
||||
.tag-inline,
|
||||
.categories,
|
||||
.grade,
|
||||
.Url,
|
||||
.tabloid {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.singleTag {
|
||||
background: #ececec;
|
||||
border-radius: 3px;
|
||||
margin: 0 2px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.taginput {
|
||||
width: 300px;
|
||||
height: 32px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.removeTag {
|
||||
margin-top: 10px;
|
||||
margin-left: 1px;
|
||||
font-size: 16px;
|
||||
height: 12px;
|
||||
color: #ddd;
|
||||
transition: color .3s ease-in;
|
||||
vertical-align: -1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.addTagsBtn {
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #349edf;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
#select-type,
|
||||
#select-categories,
|
||||
#select-grade {
|
||||
width: 138px;
|
||||
height: 32px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.required {
|
||||
margin-left: 2px;
|
||||
font-size: 12px;
|
||||
color: #ca0c16;
|
||||
}
|
||||
|
||||
#articleUrl {
|
||||
width: 300px;
|
||||
height: 32px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
62
index/src/app/pages/write/write.component.html
Normal file
62
index/src/app/pages/write/write.component.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<div style="margin-top: -80px;margin-bottom: -130px;z-index: 1000">
|
||||
<div class="con">
|
||||
<input type="text" [(ngModel)]="article.title" id="title">
|
||||
<button 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)]="showPupup" nzTitle="发布博文" (nzOnCancel)="showPupup=false" (nzOnOk)="publishArticle()">
|
||||
<div class="am-modal-bd">
|
||||
<div class="article-setting">
|
||||
|
||||
<div class="article-type row">
|
||||
<label class="publish-tag">文章类型<strong>:</strong></label>
|
||||
<div class="type">
|
||||
<select id="select-type" [(ngModel)]="article.type">
|
||||
<option>请选择</option>
|
||||
<option [ngValue]="true">原创</option>
|
||||
<option [ngValue]="false">转载</option>
|
||||
</select>
|
||||
<span class="required">*</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="article-type row">
|
||||
<label class="publish-tag">博客分类<strong>:</strong></label>
|
||||
<div class="categories">
|
||||
<select id="select-categories" [(ngModel)]="article.category">
|
||||
<option class="categoriesOption" value="">请选择</option>
|
||||
<option class="categoriesOption" *ngFor="let category of categoryService.categories"
|
||||
value={{category.name}}>{{category.name}}</option>
|
||||
</select>
|
||||
<span class="required">*</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="article-tag row">
|
||||
<div class="tags">
|
||||
<div class="tag-inline">
|
||||
<label class="publish-tag">文章标签<strong>:</strong></label>
|
||||
<div class="tag">
|
||||
</div>
|
||||
<input class="taginput" placeholder="每个标签以英文','结束" [(ngModel)]="article.tags"><span
|
||||
class="required">*</span>
|
||||
</div>
|
||||
<span style="display: block;margin-left:67px;" *ngIf="article.tags">
|
||||
<span *ngFor="let tag of article.tags.split(',')" class="singleTag">{{tag}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="articleUrlHide row" *ngIf="!article.type">
|
||||
<label class="publish-tag" style="display: inline-block">原文链接<strong>:</strong></label>
|
||||
<div class="url" style="display: inline-block">
|
||||
<input type="text" id="articleUrl" [(ngModel)]="article.url" placeholder=" 请输入原文链接">
|
||||
<span class="required">*</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nz-modal>
|
||||
25
index/src/app/pages/write/write.component.spec.ts
Normal file
25
index/src/app/pages/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();
|
||||
});
|
||||
});
|
||||
183
index/src/app/pages/write/write.component.ts
Normal file
183
index/src/app/pages/write/write.component.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import {Component, OnInit, Output, Input, EventEmitter} from '@angular/core';
|
||||
import {EditorConfig} from '../../class/editor-config';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Router} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {ArticleService} from '../../services/article/article.service';
|
||||
import {CategoryService} from '../../services/category/category.service';
|
||||
import {ArticleReq} from '../../class/articleReq';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-write',
|
||||
templateUrl: './write.component.html',
|
||||
styleUrls: ['./write.component.css']
|
||||
})
|
||||
export class WriteComponent implements OnInit {
|
||||
|
||||
constructor(public articleService: ArticleService,
|
||||
private routerinfo: ActivatedRoute,
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private message: NzMessageService,
|
||||
public categoryService: CategoryService,
|
||||
public userService: UserService) {
|
||||
titleService.setTitle('小海博客|创作');
|
||||
}
|
||||
|
||||
public obj: any;
|
||||
show = false;
|
||||
msg: any;
|
||||
conf = new EditorConfig();
|
||||
articleId;
|
||||
isUpdate = false;
|
||||
|
||||
public article: ArticleReq = new ArticleReq();
|
||||
|
||||
showPupup = false;
|
||||
|
||||
// 同步属性内容
|
||||
syncModel(str): void {
|
||||
this.article.mdContent = str;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.articleId = this.routerinfo.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();
|
||||
// 用户权限判断
|
||||
if (!this.userService.userInfo) {
|
||||
this.userService.getUserInfo().subscribe(data => {
|
||||
if (!data.result || data.result.role !== 'admin') {
|
||||
this.message.info('你暂时无发布文章的权限,所写文章将暂存在本地');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this.userService.userInfo.role !== 'admin') {
|
||||
this.message.info('你暂时无发布文章的权限,所写文章将暂存在本地');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置高度
|
||||
*/
|
||||
setSuitableHeight() {
|
||||
this.conf.height = (window.innerHeight - 80 - 50) + '';
|
||||
}
|
||||
|
||||
// 提交按钮的事件
|
||||
articleSubmit() {
|
||||
if (this.article.title === '' || this.article.mdContent === '') {
|
||||
this.message.warning(this.article.title === '' ? '标题不能为空' : '文章不能为空');
|
||||
return;
|
||||
}
|
||||
this.showPupup = true;
|
||||
if (!this.categoryService.categories) {
|
||||
this.categoryService.getAllCategory();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章数据提交
|
||||
*/
|
||||
publishArticle() {
|
||||
// 去除空值
|
||||
this.article.tags = this.article.tags.split(',').filter(item => item !== '').toString();
|
||||
if (this.article.tags === '') {
|
||||
this.message.warning('文章标签不能为空');
|
||||
return;
|
||||
}
|
||||
if (this.article.type !== (true || false)) {
|
||||
this.message.warning('文章类型不能为空');
|
||||
return;
|
||||
}
|
||||
if (this.article.category === '') {
|
||||
this.message.warning('文章分类不能为空');
|
||||
return;
|
||||
}
|
||||
if (!this.article.type) {
|
||||
const regExp = /^http(s|):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$/;
|
||||
if (regExp.test(this.article.url)) {
|
||||
this.message.warning('原文链接不合法');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 文章 暂存
|
||||
localStorage.setItem('tmpArticle', JSON.stringify(this.article));
|
||||
|
||||
this.article.url = this.article.type ? null : this.article.url;
|
||||
|
||||
if (!this.isUpdate) {
|
||||
// 非文章更新
|
||||
this.articleService.createArticle(this.article).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
// TODO 成功
|
||||
this.message.success('发布成功,即将转跳');
|
||||
localStorage.removeItem('tmpArticle');
|
||||
|
||||
setTimeout(() => {
|
||||
this.router.navigateByUrl('article/' + data.result.id);
|
||||
}, 2500);
|
||||
}
|
||||
if (data.code === 301) {
|
||||
// 未登陆
|
||||
this.router.navigateByUrl('login');
|
||||
}
|
||||
if (data.code === 302) {
|
||||
this.message.error('你没有发布文章的权限');
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// 文章更新
|
||||
this.articleService.updateArticle(this.article).subscribe(data => {
|
||||
// TODO 未登陆
|
||||
if (data.code === 0) {
|
||||
this.message.success('更新成功,即将转跳');
|
||||
localStorage.removeItem('tmpArticle');
|
||||
setTimeout(() => {
|
||||
this.router.navigateByUrl('article/' + data.result.id);
|
||||
}, 2500);
|
||||
} else if (data.code === 301) {
|
||||
this.router.navigateByUrl('login');
|
||||
} else if (data.code === 302) {
|
||||
this.message.error('你没有更新文章的权限');
|
||||
} else {
|
||||
this.message.error('失败,原因:' + data.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章 for update
|
||||
*/
|
||||
getArticle() {
|
||||
this.articleService.getArticleById(this.articleId, true).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
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;
|
||||
} else if (data.code === 201) {
|
||||
// 文章不存在
|
||||
this.message.error('文章不存在');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
12
index/src/app/services/article/article.service.spec.ts
Normal file
12
index/src/app/services/article/article.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ArticleService } from './article.service';
|
||||
|
||||
describe('ArticleService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: ArticleService = TestBed.get(ArticleService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
97
index/src/app/services/article/article.service.ts
Normal file
97
index/src/app/services/article/article.service.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
import {Page} from '../../class/page';
|
||||
import {Article} from '../../class/article';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Data} from '../../class/data';
|
||||
import {ArticleReq} from '../../class/articleReq';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ArticleService {
|
||||
|
||||
|
||||
constructor(public http: HttpService) {
|
||||
}
|
||||
|
||||
// 存储所有已经请求过的数据<首页数据>
|
||||
pageList: Page<Article>[] = [];
|
||||
|
||||
// 最后一次请求后的数据集<首页数据>
|
||||
currentPage: Page<Article>;
|
||||
|
||||
// 通过分类获取的article
|
||||
public currentArticleOfCategory: Page<Article>;
|
||||
// 通过分类获取的article
|
||||
public currentArticleOfTag: Page<Article>;
|
||||
|
||||
|
||||
/**
|
||||
* 获取文章
|
||||
* @param pageNum 页码数
|
||||
* @param pageSize 单页数据量
|
||||
*/
|
||||
getArticle(pageNum: number, pageSize: number): object {
|
||||
const articlePage = this.exist(pageNum, pageSize);
|
||||
if (articlePage) {
|
||||
return articlePage;
|
||||
}
|
||||
const observable = this.http.get('/articles?page=' + pageNum + '&count=' + pageSize);
|
||||
observable.subscribe((data: any) => {
|
||||
if (data.code === 0) {
|
||||
this.currentPage = data.result;
|
||||
this.pageList.push(data.result);
|
||||
}
|
||||
});
|
||||
return observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类获取文章
|
||||
* @param name 分类名
|
||||
*/
|
||||
getArticleByCategory(name: string) {
|
||||
return this.http.get('/articles/category/' + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签获取文章
|
||||
* @param name 标签名
|
||||
*/
|
||||
getArticleByTag(name: string) {
|
||||
return this.http.get('/articles/tag/' + name);
|
||||
}
|
||||
|
||||
getArticleById(id: number, update: boolean = false) {
|
||||
return this.http.get('/article/articleID/' + id + '?update=' + update);
|
||||
}
|
||||
|
||||
|
||||
createArticle(article: ArticleReq) {
|
||||
return this.http.post('/admin/article/create', article, true);
|
||||
}
|
||||
|
||||
updateArticle(article: ArticleReq) {
|
||||
return this.http.put('/admin/article/update', article);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断并返回数据
|
||||
* @param pageNum 页码数
|
||||
* @param pageSize 单页数据量
|
||||
*/
|
||||
private exist(pageNum: number, pageSize: number): Page<Article> {
|
||||
if (this.currentPage == null) {
|
||||
return null;
|
||||
}
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < this.pageList.length; i++) {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
if (this.pageList[i].pageNum == pageNum && this.pageList[i].pageSize == pageSize) {
|
||||
return this.pageList[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
12
index/src/app/services/category/category.service.spec.ts
Normal file
12
index/src/app/services/category/category.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CategoryService } from './category.service';
|
||||
|
||||
describe('CategoryService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: CategoryService = TestBed.get(CategoryService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
27
index/src/app/services/category/category.service.ts
Normal file
27
index/src/app/services/category/category.service.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
import {Category} from '../../class/category';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CategoryService {
|
||||
|
||||
constructor(public http: HttpService) {
|
||||
}
|
||||
|
||||
categories: Category[];
|
||||
|
||||
getAllCategory() {
|
||||
const observable = this.http.get('/categories');
|
||||
observable.subscribe((data) => {
|
||||
if (data.code === 0) {
|
||||
this.categories = data.result;
|
||||
}
|
||||
}
|
||||
);
|
||||
return observable;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
12
index/src/app/services/comment/comment.service.spec.ts
Normal file
12
index/src/app/services/comment/comment.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CommentService } from './comment.service';
|
||||
|
||||
describe('CommentService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: CommentService = TestBed.get(CommentService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
124
index/src/app/services/comment/comment.service.ts
Normal file
124
index/src/app/services/comment/comment.service.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
import {Page} from '../../class/page';
|
||||
import {LeaveMsg} from '../../class/LeaveMsg';
|
||||
import {CommentReq} from '../../class/commentReq';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CommentService {
|
||||
|
||||
constructor(public http: HttpService) {
|
||||
}
|
||||
|
||||
// 存放
|
||||
leaveMsgPage: Page<LeaveMsg> = new Page();
|
||||
|
||||
commentPage: Page<LeaveMsg> = new Page();
|
||||
|
||||
|
||||
/**
|
||||
* 获取留言
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 单页数据数量
|
||||
*/
|
||||
getLeaveMsg(pageNum: number, pageSize: number) {
|
||||
const observable = this.http.get('/leaveMsg?count=' + pageSize + '&page=' + pageNum);
|
||||
observable.subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.leaveMsgPage = data.result;
|
||||
this.getResponseLeaveMsg();
|
||||
}
|
||||
});
|
||||
return observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章的评论
|
||||
* @param articleId 文章id
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 单页数量
|
||||
*/
|
||||
getPageComment(articleId: number, pageNum: number, pageSize: number) {
|
||||
const observable = this.http.get('/comments?articleId=' + articleId + '&count=' + pageSize + '&page=' + pageNum);
|
||||
observable.subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.commentPage = data.result;
|
||||
this.getResponseComment();
|
||||
}
|
||||
});
|
||||
return observable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取留言的回复
|
||||
*/
|
||||
getResponseLeaveMsg() {
|
||||
if (!this.leaveMsgPage.list) {
|
||||
return;
|
||||
}
|
||||
this.leaveMsgPage.list.forEach(leaveMsg => {
|
||||
if (leaveMsg.responseId != null && leaveMsg.responseId !== '') {
|
||||
this.getByPid(leaveMsg.id).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
leaveMsg.child = data.result.list;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评论的回复
|
||||
*/
|
||||
getResponseComment() {
|
||||
if (!this.commentPage.list) {
|
||||
return;
|
||||
}
|
||||
this.commentPage.list.forEach(comment => {
|
||||
if (comment.responseId != null && comment.responseId !== '') {
|
||||
this.getByPid(comment.id).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
comment.child = data.result.list;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过父评论 获取回复
|
||||
* @param pid 父评论id
|
||||
*/
|
||||
getByPid(pid: number) {
|
||||
return this.http.get('/comment/pid/' + pid + '?count=5&page=1');
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交评论/留言 并加入缓存数据中
|
||||
* @param submitBody 请求体
|
||||
*/
|
||||
submitComment(submitBody: CommentReq) {
|
||||
this.http.post('/user/comment/create', submitBody, true).subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
if (!submitBody.comment) {
|
||||
this.leaveMsgPage.list.unshift(data.result);
|
||||
} else {
|
||||
this.commentPage.list.unshift(data.result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 回复评论/留言
|
||||
* @param responseComment 请求体
|
||||
*/
|
||||
rely(responseComment: CommentReq) {
|
||||
return this.http.post('/user/comment/create', responseComment, true);
|
||||
}
|
||||
}
|
||||
12
index/src/app/services/count/count.service.spec.ts
Normal file
12
index/src/app/services/count/count.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CountService } from './count.service';
|
||||
|
||||
describe('CountService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: CountService = TestBed.get(CountService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
24
index/src/app/services/count/count.service.ts
Normal file
24
index/src/app/services/count/count.service.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
import {Count} from '../../class/count';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CountService {
|
||||
|
||||
constructor(public http: HttpService) {
|
||||
}
|
||||
|
||||
count: Count;
|
||||
|
||||
getCount() {
|
||||
const observable = this.http.get('/counts');
|
||||
observable.subscribe((data: any) => {
|
||||
if (data.code === 0) {
|
||||
this.count = data.result;
|
||||
}
|
||||
});
|
||||
return observable;
|
||||
}
|
||||
}
|
||||
12
index/src/app/services/http.service.spec.ts
Normal file
12
index/src/app/services/http.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HttpService } from './http.service';
|
||||
|
||||
describe('HttpService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: HttpService = TestBed.get(HttpService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
110
index/src/app/services/http.service.ts
Normal file
110
index/src/app/services/http.service.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||
import {environment} from '../../environments/environment';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Data} from '../class/data';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HttpService {
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
this.host = environment.host;
|
||||
const item = localStorage.getItem('token');
|
||||
this.token = item == null ? '' : item;
|
||||
this.httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
Accept: '*/*',
|
||||
Authorization: this.token
|
||||
}),
|
||||
withCredentials: true
|
||||
};
|
||||
}
|
||||
|
||||
// 请求的主机地址
|
||||
public host: string;
|
||||
private token: string;
|
||||
|
||||
/**
|
||||
* http请求配置
|
||||
*/
|
||||
private httpOptions: object;
|
||||
|
||||
/**
|
||||
* get 请求
|
||||
* @param path 路径
|
||||
*/
|
||||
get(path: string): Observable<Data> {
|
||||
return this.http.get<Data>(this.getPath(path), this.httpOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* post请求
|
||||
* @param path 路径
|
||||
* @param reqBody 请求体
|
||||
* @param isJson 请求数据是否是json格式
|
||||
*/
|
||||
post(path: string, reqBody: object, isJson: boolean): Observable<Data> {
|
||||
const Options = {
|
||||
headers: new HttpHeaders({
|
||||
Accept: '*/*',
|
||||
Authorization: this.token,
|
||||
ContentType: isJson ? 'application/json' : 'application/x-www-form-urlencoded'
|
||||
}),
|
||||
withCredentials: true
|
||||
};
|
||||
let submitBody = '';
|
||||
if (!isJson) {
|
||||
for (const key in reqBody) {
|
||||
// 跳过值为null的参数请求
|
||||
if (reqBody[key] == null || reqBody[key] === 'null') {
|
||||
continue;
|
||||
}
|
||||
submitBody = submitBody + '&' + key + '=' + reqBody[key];
|
||||
}
|
||||
submitBody = submitBody.substring(1);
|
||||
}
|
||||
return this.http.post<Data>(this.getPath(path), isJson ? reqBody : submitBody, Options);
|
||||
}
|
||||
|
||||
/**
|
||||
* put 请求
|
||||
* @param path 请求路径
|
||||
* @param reqBody 请求体
|
||||
*/
|
||||
put(path: string, reqBody: object): Observable<Data> {
|
||||
return this.http.put<Data>(this.getPath(path), reqBody, this.httpOptions);
|
||||
}
|
||||
|
||||
visit() {
|
||||
this.post('/visit', null, true).subscribe(data => {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查path 并拼接
|
||||
* @param path 请求路径
|
||||
* @return 拼接后的url
|
||||
*/
|
||||
private getPath(path: string): string {
|
||||
if (path == null || path.length === 0 || path.substr(0, 1) !== '/') {
|
||||
throw new Error('路径不合法');
|
||||
}
|
||||
return this.host + path;
|
||||
}
|
||||
|
||||
setToken(t: string) {
|
||||
if (t == null) {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('token', t);
|
||||
this.token = t;
|
||||
}
|
||||
|
||||
removeToken() {
|
||||
localStorage.removeItem('token');
|
||||
this.token = ''
|
||||
}
|
||||
}
|
||||
12
index/src/app/services/link/link.service.spec.ts
Normal file
12
index/src/app/services/link/link.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LinkService } from './link.service';
|
||||
|
||||
describe('LinkService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: LinkService = TestBed.get(LinkService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
28
index/src/app/services/link/link.service.ts
Normal file
28
index/src/app/services/link/link.service.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LinkService {
|
||||
|
||||
constructor(public http: HttpService) {
|
||||
}
|
||||
|
||||
public Links: { id: number, name: string, url: string };
|
||||
|
||||
apply(link: {
|
||||
name: string,
|
||||
url: string
|
||||
}) {
|
||||
return this.http.post('/apply', link, false);
|
||||
}
|
||||
|
||||
getLinks() {
|
||||
this.http.get('/links').subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.Links = data.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
12
index/src/app/services/tag/tag.service.spec.ts
Normal file
12
index/src/app/services/tag/tag.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TagService } from './tag.service';
|
||||
|
||||
describe('TagService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: TagService = TestBed.get(TagService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
26
index/src/app/services/tag/tag.service.ts
Normal file
26
index/src/app/services/tag/tag.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
import {Tag} from '../../class/tag';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TagService {
|
||||
|
||||
constructor(public http: HttpService) {
|
||||
}
|
||||
|
||||
tagCloudList: Tag[];
|
||||
|
||||
getTagCloud() {
|
||||
const observable = this.http.get('/tags/nac');
|
||||
observable.subscribe((data: any) => {
|
||||
if (data.code === 0) {
|
||||
this.tagCloudList = data.result;
|
||||
}
|
||||
});
|
||||
return observable;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
12
index/src/app/services/update/web-update.service.spec.ts
Normal file
12
index/src/app/services/update/web-update.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WebUpdateService } from './web-update.service';
|
||||
|
||||
describe('WebUpdateService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: WebUpdateService = TestBed.get(WebUpdateService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
34
index/src/app/services/update/web-update.service.ts
Normal file
34
index/src/app/services/update/web-update.service.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
import {UpdateInfo} from '../../class/updateInfo';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebUpdateService {
|
||||
|
||||
constructor(public http: HttpService) {
|
||||
}
|
||||
|
||||
public updateInfoList: UpdateInfo[];
|
||||
|
||||
public lastestUpdateTime: string;
|
||||
|
||||
// when you fell unhappy,look at the sky, the sun is shining the birds are singing
|
||||
// And you? should be smiling
|
||||
getUpdateInfo() {
|
||||
this.http.get('/webUpdate').subscribe((data: any) => {
|
||||
if (data.code === 0) {
|
||||
this.updateInfoList = data.result.reverse();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getLastestUpdateTime() {
|
||||
this.http.get('/lastestUpdateTime').subscribe(data => {
|
||||
if (data.code === 0) {
|
||||
this.lastestUpdateTime = data.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
12
index/src/app/services/user/user.service.spec.ts
Normal file
12
index/src/app/services/user/user.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserService } from './user.service';
|
||||
|
||||
describe('UserService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: UserService = TestBed.get(UserService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
100
index/src/app/services/user/user.service.ts
Normal file
100
index/src/app/services/user/user.service.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
import {User} from '../../class/user';
|
||||
import {LoginReq} from '../../class/loginReq';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
|
||||
constructor(public http: HttpService) {
|
||||
}
|
||||
|
||||
userInfo: User;
|
||||
|
||||
// 刚注册完账户 实现自动填充账户的临时存储 登录成功即置空
|
||||
tempUser: LoginReq;
|
||||
|
||||
loginModalType: 'login' | 'registration' = 'login';
|
||||
loginModalVisible: boolean = false;
|
||||
|
||||
|
||||
showModal(type: 'login' | 'registration') {
|
||||
this.loginModalType = type;
|
||||
this.loginModalVisible = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
getUserInfo() {
|
||||
const observable = this.http.get('/user/userInfo');
|
||||
observable.subscribe((data: any) => {
|
||||
if (data.code === 0) {
|
||||
this.userInfo = data.result;
|
||||
}
|
||||
});
|
||||
return observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销登录
|
||||
*/
|
||||
logout() {
|
||||
this.http.get('/logout').subscribe((data: any) => {
|
||||
if (data.code === 0) {
|
||||
this.userInfo = null;
|
||||
this.http.removeToken();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @param loginReq 请求体
|
||||
*/
|
||||
login(loginReq: {
|
||||
'email': string,
|
||||
'isRememberMe': boolean,
|
||||
'password': string
|
||||
}) {
|
||||
const observable = this.http.post('/login', loginReq, true);
|
||||
observable.subscribe((data: any) => {
|
||||
if (data.code === 0) {
|
||||
this.userInfo = data.result;
|
||||
this.loginModalVisible = false;
|
||||
this.http.setToken(data.result.token);
|
||||
}
|
||||
});
|
||||
return observable;
|
||||
}
|
||||
|
||||
registration(emailStr: string, pwd: string) {
|
||||
const submitBody = {
|
||||
email: emailStr,
|
||||
password: pwd
|
||||
};
|
||||
return this.http.post('/registration', submitBody, false);
|
||||
// 注册成功 -> 登录 在component里面实现了
|
||||
}
|
||||
|
||||
|
||||
emailVerify(reqBody) {
|
||||
return this.http.post('/emailVerify', reqBody, false);
|
||||
}
|
||||
|
||||
resetPWd(reqBody) {
|
||||
return this.http.post('/resetPwd', reqBody, false);
|
||||
}
|
||||
|
||||
sendResetPwdEmail(emailStr: string) {
|
||||
return this.http.post('/sendResetPwdEmail', {email: emailStr}, false);
|
||||
}
|
||||
|
||||
imgCodeVerify(codeStr: string) {
|
||||
return this.http.post('/verCode', {code: codeStr}, false);
|
||||
}
|
||||
|
||||
}
|
||||
0
index/src/assets/.gitkeep
Normal file
0
index/src/assets/.gitkeep
Normal file
4450
index/src/assets/editor.md/css/editormd.css
Normal file
4450
index/src/assets/editor.md/css/editormd.css
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user