重构界面ui

This commit is contained in:
小海
2020-04-06 20:48:40 +08:00
parent f2791ee150
commit 3ea8f63abc
601 changed files with 95141 additions and 90135 deletions

View File

@@ -0,0 +1,12 @@
import {TestBed} from '@angular/core/testing';
import {ApiService} from './api.service';
describe('ApiService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ApiService = TestBed.get(ApiService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,345 @@
import {Injectable} from '@angular/core';
import {Article} from '../class/Article';
import {HttpService} from './http/http.service';
import {PageList} from '../class/pageList';
import {ErrDispatch} from '../class/ErrDispatch';
import {ArticleReq} from '../class/ArticleReq';
import {Category} from '../class/Category';
import {Comment} from '../class/Comment';
import {CommentReq} from '../class/CommentReq';
import {Link} from '../class/Link';
import {User} from '../class/User';
import {LoginReq} from '../class/LoginReq';
import {Observable, Observer, of} from 'rxjs';
import {Response} from '../class/Response';
import {HttpClient} from '@angular/common/http';
import {LocalStorageService} from '../utils/local-storage.service';
@Injectable({
providedIn: 'root'
})
export class ApiService extends HttpService {
constructor(httpClient: HttpClient,
localStorageService: LocalStorageService) {
super(httpClient, localStorageService);
}
setErrDispatch(errDispatch: ErrDispatch) {
super.setErrDispatch(errDispatch);
}
createArticle(article: ArticleReq) {
article.id = null;
return super.Service<Article>({
path: '/admin/article/create',
contentType: 'application/json',
method: 'POST',
data: article
});
}
articles(pageNumber: number = 1, pageSize: number = 5) {
return super.Service<PageList<Article>>({
path: '/articles',
method: 'GET',
queryParam: {
page: pageNumber,
count: pageSize
}
});
}
updateArticle(article: ArticleReq) {
return super.Service<Article>({
path: '/admin/article/update',
method: 'PUT',
contentType: 'application/json',
data: article
});
}
getArticle(articleId: number, is4Update: boolean = false) {
return super.Service<Article>({
path: `/article/articleID/${articleId}`,
method: 'GET',
queryParam: {update: is4Update},
});
}
articlesByCategory(category: string, pageNumber: number = 1, pageSize: number = 10) {
return super.Service<PageList<Article>>({
path: `/articles/category/${category}`,
method: 'GET',
queryParam: {
page: pageNumber,
count: pageSize
}
});
}
articlesByTag(tag: string, pageNumber: number = 1, pageSize: number = 10) {
return super.Service<PageList<Article>>({
path: `/articles/tag/${tag}`,
method: 'GET',
queryParam: {
page: pageNumber,
count: pageSize
}
});
}
categories() {
return super.Service<Category[]>({
path: '/categories',
method: 'GET'
});
}
tags(pageNumber: number = 1, pageSize: number = 10) {
return super.Service<Category[]>({
path: '/tags',
method: 'GET',
queryParam: {
page: pageNumber,
count: pageSize
}
});
}
tagsNac() {
return super.Service<{ name: string, size: number }[]>({
path: '/tags/nac',
method: 'GET'
});
}
getCommentByPid(pid: number, pageNumber: number = 1, pageSize: number = 10) {
return super.Service<PageList<Comment>[]>({
path: `/comment/pid/${pid}`,
method: 'GET',
queryParam: {
page: pageNumber,
count: pageSize
}
});
}
comments(articleID: number, pageSize: number = 10, pageNumber: number = 1) {
return super.Service<PageList<Comment>>({
path: '/comments',
method: 'GET',
queryParam: {
articleId: articleID,
count: pageSize,
page: pageNumber
}
});
}
leaveMsg(pageSize: number = 10, pageNumber: number = 1) {
return super.Service<PageList<Comment>>({
path: '/leaveMsg',
method: 'GET',
queryParam: {
count: pageSize,
page: pageNumber
}
});
}
createComment(commentReq: CommentReq) {
return super.Service<Comment>({
path: '/user/comment/create',
method: 'POST',
contentType: 'application/json',
data: commentReq
});
}
counts() {
return super.Service<{
articleCount: number,
visitorCount: number,
categoryCount: number,
leaveMsgCount: number,
tagCount: number,
commentCount: number
}>({
path: '/counts',
method: 'GET'
});
}
applyLink(link: Link) {
return super.Service<string>({
path: '/apply',
method: 'POST',
queryParam: {
name: link.name,
url: link.url
}
});
}
links() {
return super.Service<Link[]>({
path: '/links',
method: 'GET',
});
}
verifyImgCode(codeStr: string) {
return super.Service<string>({
path: '/verCode',
method: 'POST',
queryParam: {code: codeStr}
});
}
login(loginReq: LoginReq) {
const observable = super.Service<User>({
path: '/login',
method: 'POST',
contentType: 'application/json',
data: loginReq
});
let observer: Observer<Response<User>>;
const oob = new Observable<Response<User>>(o => observer = o);
observable.subscribe({
next: o => {
if (o.code === 0) {
// 登录成功
this.localStorageService.setToken(o.result.token);
this.localStorageService.setUser(o.result);
}
observer.next(o);
observer.complete();
},
error: err => {
observer.error(err);
observer.complete();
}
});
return oob;
}
logout() {
this.localStorageService.clear();
return super.Service<string>({
path: '/logout',
method: 'GET',
});
}
registration(emailStr: string, pwd: string) {
return super.Service<boolean>({
path: '/registration',
method: 'POST',
queryParam: {
email: emailStr,
password: pwd
}
});
}
resetPwd(idStr: string, emailStr: string, pwdStr: string) {
return super.Service<string>({
path: '/resetPwd',
method: 'POST',
queryParam: {
verifyId: idStr,
email: emailStr,
pwd: pwdStr
}
});
}
emailVerify(idStr: string, emailStr: string) {
return super.Service<void>({
path: '/emailVerify',
method: 'POST',
queryParam: {
verifyId: idStr,
email: emailStr
}
});
}
sendResetPwdEmail(emailStr: string) {
return super.Service<string>({
path: '/sendResetPwdEmail',
method: 'POST',
queryParam: {email: emailStr}
});
}
sendVerifyEmail(emailStr: string) {
return super.Service<string>({
path: '/sendVerifyEmail',
method: 'POST',
queryParam: {email: emailStr}
});
}
userInfo() {
// 判断本地缓存的用户信息是否符合要求,符合要求返回本地缓存
const user = this.localStorageService.getUser();
if (this.localStorageService.isLogin() && user && !this.localStorageService.checkNeedNet()) {
return of<Response<User>>(new Response<User>(user));
}
// 不符合 请求网络数据并更新缓存
const observable = super.Service<User>({
path: '/user/userInfo',
method: 'GET',
});
let observer: Observer<Response<User>>;
const oob = new Observable<Response<User>>(o => observer = o);
observable.subscribe({
next: o => {
this.localStorageService.setUser(o.result);
observer.next(o);
observer.complete();
},
error: err => {
// console.debug('登录过期 token错误 等等');
this.localStorageService.removeToken();
observer.error(err);
observer.complete();
}
});
return oob;
}
visit() {
return super.Service<User>({
path: '/visit',
method: 'POST',
});
}
webUpdate() {
return super.Service<{ id: number, info: string, time: string }[]>({
path: '/webUpdate',
method: 'GET'
});
}
lastestUpdateTime() {
return super.Service<string>({
path: '/lastestUpdateTime',
method: 'GET'
});
}
bingPic() {
return super.Service<string>({
path: '/bingPic',
method: 'GET'
});
}
}

View File

@@ -0,0 +1,15 @@
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();
});
it('should be valid', () => {
const service: HttpService = TestBed.get(HttpService);
});
});

View File

@@ -0,0 +1,127 @@
import {Injectable} from '@angular/core';
import {RequestObj} from '../../class/Request';
import {HttpClient} from '@angular/common/http';
import {environment} from '../../../environments/environment';
import {LocalStorageService} from '../../utils/local-storage.service';
import {Response} from '../../class/Response';
import {Observable, Observer, Subject} from 'rxjs';
import {ErrDispatch} from '../../class/ErrDispatch';
import {multicast} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class HttpService {
constructor(private httpClient: HttpClient,
protected localStorageService: LocalStorageService) {
}
private errorDispatch: ErrDispatch;
setErrDispatch(errDispatch: ErrDispatch) {
this.errorDispatch = errDispatch;
}
Service<T>(request: RequestObj) {
// 设置默认值
request.contentType = request.contentType == null ? 'application/x-www-form-urlencoded' : 'application/json';
request.header = {
'Content-Type': request.contentType
};
const token = this.localStorageService.getToken();
if (token != null) {
request.header.Authorization = token;
}
request.path = this.checkUrl(request);
let observable: Observable<Response<T>>;
switch (request.method) {
case 'GET':
observable = this.get<Response<T>>(request);
break;
case 'DELETE':
observable = this.delete<Response<T>>(request);
break;
case 'PUT':
observable = this.put<Response<T>>(request);
break;
case 'POST':
observable = this.post<Response<T>>(request);
break;
}
let observer: Observer<Response<T>>;
const oob = new Observable<Response<T>>(o => observer = o);
observable.subscribe(o => {
if (o.code) {
observer.error(o);
if (this.errorDispatch) {
this.errorDispatch.errHandler(o.code, o.msg, request);
}
} else {
observer.next(o);
}
observer.complete();
});
return oob;
}
private get<T>(request: RequestObj) {
return this.httpClient.get<T>(request.path,
{
headers: request.header,
withCredentials: true
});
}
private post<T>(request: RequestObj) {
return this.httpClient.post<T>(request.path, request.data,
{
headers: request.header,
withCredentials: true
});
}
private put<T>(request: RequestObj) {
return this.httpClient.put<T>(request.path, request.data,
{
headers: request.header,
withCredentials: true
});
}
private delete<T>(request: RequestObj) {
return this.httpClient.delete<T>(request.path,
{
headers: request.header,
withCredentials: true
});
}
/**
* 验证并且处理拼接 URl
* @param req Request
*/
private checkUrl(req: RequestObj): string {
let tmpUrl = environment.host;
if (req.path.length === 0) {
return tmpUrl;
}
if (req.path.substr(0, 1) !== '/') {
tmpUrl += '/';
}
let queryStr = '';
const keys = req.queryParam == null ? [] : Object.keys(req.queryParam);
if (keys.length === 0) {
return tmpUrl + req.path;
}
for (const key of keys) {
queryStr += '&' + key + '=' + req.queryParam[key];
}
queryStr = queryStr.substr(1, queryStr.length);
return tmpUrl + req.path + '?' + queryStr;
}
}

View File

@@ -1,41 +1,32 @@
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 {
}
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
const routes: Routes = [
{path: '', pathMatch: 'full', loadChildren: () => import('./view/index/index.module').then(mod => mod.IndexModule)},
{path: 'update', loadChildren: () => import('./view/update/update.module').then(mod => mod.UpdateModule)},
{path: 'article/:id', loadChildren: () => import('./view/article/article.module').then(mod => mod.ArticleModule)},
{path: 'tags', loadChildren: () => import('./view/tag/tag.module').then(mod => mod.TagModule)},
{path: 'categories', loadChildren: () => import('./view/category/category.module').then(mod => mod.CategoryModule)},
// {path: 'leaveMsg', loadChildren: () => import('./view/leave-msg/leave-msg.module').then(mod => mod.LeaveMsgModule)},
{path: 'emailVerify', loadChildren: () => import('./view/email-verify/email-verify.module').then(mod => mod.EmailVerifyModule)},
{path: 'resetPwd', loadChildren: () => import('./view/reset-pwd/reset-pwd.module').then(mod => mod.ResetPwdModule)},
{path: 'write', loadChildren: () => import('./view/write/write.module').then(mod => mod.WriteModule)},
{path: 'links', loadChildren: () => import('./view/link/link.module').then(mod => mod.LinkModule)},
{
path: 'user', loadChildren: () => import('./view/login-registration/login-registration.module')
.then(mod => mod.LoginRegistrationModule)
},
{path: '**', loadChildren: () => import('./view/page-not-found/page-not-found.module').then(mod => mod.PageNotFoundModule)}
];
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [RouterModule]
})
export class AppRoutingModule {
}

View File

@@ -1,21 +1,31 @@
<app-header></app-header>
<div style="height: 80px"></div>
<app-header (loginEvent)="login()" (registrationEvent)="registration()" #headerComponent></app-header>
<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-back-top [nzTemplate]="backToTop"></nz-back-top>
<ng-template #backToTop>
<button style=" height: 60px;width: 60px;border-radius: 50%;" nz-button title="回到顶部">
<i nz-icon nzType="rocket" nzTheme="outline"></i>
</button>
</ng-template>
<nz-modal
[(nzVisible)]="loginModal" (nzOnCancel)="loginModal = !loginModal"
[nzContent]="content" [nzFooter]="null" nzWidth="400">
<ng-template #content>
<c-login (loginStatus)="loginStatus($event)" [showSendEmail]="false"></c-login>
</ng-template>
</nz-modal>
<ng-template #footerButton>
<small style="width:100%;text-align: center">小海博客</small>
</ng-template>
<nz-modal
[(nzVisible)]="regModal" (nzOnCancel)="regModal = !regModal" [nzContent]="content2"
[nzFooter]="null" nzWidth="400">
<ng-template #content2>
<c-registration (regStatus)="regModal = !$event" (regAccount)="cons($event)"></c-registration>
</ng-template>
</nz-modal>

View File

@@ -0,0 +1,20 @@
#slider-container {
width: 400px;
height: 100%;
position: fixed;
top: 0;
right: 0;
overflow: hidden;
background: #4ffff5;
z-index: 100;
padding: 18px;
border: 1px solid #ddd;
border-top: none;
border-bottom: none;
}
#article-container {
padding-left: 0;
padding-right: 430px;
margin: 0;
}

View File

@@ -1,35 +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!');
});
});
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 'index'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('index');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('index app is running!');
});
});

View File

@@ -1,16 +1,38 @@
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) {
}
}
import {Component, ElementRef, OnInit, TemplateRef, ViewChild} from '@angular/core';
import {LoginReq} from './class/LoginReq';
import {HeaderComponent} from './components/header/header.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent {
loginModal: boolean = false;
regModal: boolean = false;
@ViewChild('headerComponent') header: HeaderComponent;
registration() {
// todo :: 登录
console.log('registration');
this.regModal = true;
}
login() {
// TODO :: 注册
console.log('login');
this.loginModal = true;
}
cons($event: LoginReq) {
console.log($event);
}
loginStatus(e: boolean) {
if (e) {
this.header.getInfo();
}
this.loginModal = !e;
}
}

View File

@@ -1,92 +1,38 @@
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 {
}
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
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 {HeaderComponent} from './components/header/header.component';
import {FooterComponent} from './components/footer/footer.component';
import {AppRoutingModule} from './app-routing.module';
import {LoginRegistrationModule} from './view/login-registration/login-registration.module';
registerLocaleData(zh);
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
FooterComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
NgZorroAntdModule,
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
LoginRegistrationModule
],
providers: [{provide: NZ_I18N, useValue: zh_CN}],
exports: [],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

@@ -0,0 +1,12 @@
export class Comment {
id?: number;
authorName?: string;
authorAvatarImgUrl?: string;
content: string;
articleID: number;
date?: string;
responseId: string;
pid: number;
comment: boolean;
respComment: Comment[];
}

View File

@@ -0,0 +1,27 @@
import {environment} from '../../environments/environment';
export class EditorConfig {
public width = '100%';
public height = '400';
public path = 'assets/editor/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';
}

View File

@@ -0,0 +1,5 @@
import {RequestObj} from './Request';
export interface ErrDispatch {
errHandler(code: number, msg: string, request?: RequestObj): void;
}

View File

@@ -1,11 +0,0 @@
export class LeaveMsg {
id: number;
type: number;
authorName: string;
authorAvatarImgUrl: string;
content: string;
date: string;
pid: number;
responseId: string;
child: LeaveMsg[];
}

View File

@@ -0,0 +1,5 @@
export class Link {
id?: number;
name: string;
url: string;
}

View File

@@ -0,0 +1,12 @@
import {HttpHeaders} from '@angular/common/http';
export class RequestObj {
path: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: {};
contentType?: 'application/json' | 'application/x-www-form-urlencoded';
queryParam?: {};
header?: HttpHeaders | {
[header: string]: string | string[];
};
}

View File

@@ -0,0 +1,11 @@
export class Response<T> {
code: number;
msg: string;
result: T;
date: number;
constructor(t: T) {
this.code = 0;
this.result = t;
}
}

View File

@@ -1,19 +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;
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;
}

View File

@@ -1,14 +1,14 @@
export class ArticleReq {
category: string;
id?: number;
mdContent: string;
open: boolean;
tags: string;
title: string;
type: boolean;
url?: string;
category: string;
id?: number;
mdContent: string;
open: boolean;
tags: string;
title: string;
type: boolean;
url?: string;
constructor() {
this.type = true;
}
constructor() {
this.type = true;
}
}

View File

@@ -1,6 +1,5 @@
export class Category {
id: number;
name: string;
articles: string;
size?: number;
id: number;
name: string;
articles?: number[];
}

View File

@@ -1,18 +1,18 @@
export class CommentReq {
articleID: number;
comment: boolean;
content: string;
id: number;
pid: number;
responseId: string;
id?: number;
comment: boolean;
content: string;
pid: number;
articleID: number;
responseId: string;
constructor(comment: boolean) {
this.comment = comment;
this.responseId = '';
if (!comment) {
this.articleID = -1;
constructor(comment: boolean) {
this.comment = comment;
this.responseId = '';
if (!comment) {
this.articleID = -1;
}
this.pid = -1;
this.id = null;
}
this.pid = -1;
this.id = null;
}
}
}

View File

@@ -1,7 +0,0 @@
export class Count {
visitorCount: number = 0;
leaveMsgCount: number = 0;
tagCount: number = 0;
articleCount: number = 0;
commentCount: number = 0;
}

View File

@@ -1,6 +0,0 @@
export class Data {
code: number;
msg: string;
result: any;
date: number;
}

View File

@@ -1,30 +0,0 @@
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() {
}
}

View File

@@ -1,5 +1,11 @@
export class LoginReq {
email: string;
password: string;
isRememberMe: boolean = false;
}
email: string;
isRememberMe: boolean;
password: string;
constructor(email: string, isRememberMe: boolean, password: string) {
this.email = email;
this.isRememberMe = isRememberMe;
this.password = password;
}
}

View File

@@ -1,20 +0,0 @@
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;
}

View File

@@ -0,0 +1,20 @@
export class PageList<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;
}

View File

@@ -1,6 +1,5 @@
export class Tag {
id?: number;
name: string;
articles?: string;
size: number;
id: number;
name: string;
articles?: number[];
}

View File

@@ -1,5 +0,0 @@
export class UpdateInfo {
id: number;
info: string;
time: string;
}

View File

@@ -1,9 +1,10 @@
export class User {
id: number;
email: string;
displayName: string;
emailStatus: boolean;
avatarImgUrl: string;
desc: string;
role: string;
id: number;
email: string;
displayName: string;
emailStatus: boolean;
avatarImgUrl: string;
desc: string;
role: string;
token?: string;
}

View File

@@ -1,11 +1,12 @@
<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>
<div class="footer" *ngIf="show">
<nz-divider></nz-divider>
<div>
<a href="http://www.miitbeian.gov.cn" target="_blank">
鄂ICP备18023929号
</a>
<div>
© 2019 <a href="https://www.celess.cn">小海博客</a> -
<span>郑海 </span> <span *ngIf="gName">& {{gName}} </span>版权所有
</div>
</div>
</div>

View File

@@ -1,22 +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;
}
.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;
}

View File

@@ -1,25 +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();
});
});
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();
});
});

View File

@@ -1,15 +1,29 @@
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() {
}
}
import {Component, OnInit} from '@angular/core';
import {filter} from 'rxjs/operators';
import {NavigationEnd, Route, Router, RouterEvent} from '@angular/router';
@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.less']
})
export class FooterComponent implements OnInit {
show: boolean;
constructor(private router: Router) {
this.show = true;
}
readonly gName: string;
ngOnInit() {
this.router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe((e: RouterEvent) => {
const indexOf = e.url.lastIndexOf('/');
const prefix = e.url.substr(0, indexOf === 0 ? e.url.length : indexOf);
if (prefix === '/user' || prefix === '/write') {
this.show = false;
}
});
}
}

View File

@@ -1,136 +0,0 @@
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;
}
}

View File

@@ -1,67 +1,36 @@
<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>&nbsp;首页</a></li>
<li><a class="top_bar" routerLink="/category" style="cursor: pointer;"><i nz-icon nzType="project" nzTheme="fill"></i>&nbsp;分类</a>
</li>
<li><a class="top_bar" routerLink="/tag" style="cursor: pointer;"><i nz-icon nzType="tags" nzTheme="fill"></i>&nbsp;标签</a>
</li>
<li><a class="top_bar" routerLink="/leaveMsg" style="cursor: pointer;"><i nz-icon nzType="profile" nzTheme="fill"></i>&nbsp;留言</a>
</li>
<li><a class="top_bar" routerLink="/update" style="cursor: pointer;"><i nz-icon nzType="up-square" nzTheme="fill"></i>&nbsp;更新</a>
</li>
<li><a class="top_bar" routerLink="/links" style="cursor: pointer;"><i nz-icon nzType="link" nzTheme="outline"></i>&nbsp;友链</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>&nbsp;首页</a></li>
<li nz-menu-item><a class="top_bar" routerLink="/category" style="cursor: pointer;"><i nz-icon nzType="project" nzTheme="fill"></i>&nbsp;分类</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>&nbsp;留言</a></li>
<li nz-menu-item><a class="top_bar" routerLink="/update" style="cursor: pointer;"><i nz-icon nzType="up-square" nzTheme="fill"></i>&nbsp;更新</a></li>
<li nz-menu-item><a class="top_bar" routerLink="/links" style="cursor: pointer;"><i nz-icon nzType="link" nzTheme="outline"></i>&nbsp;友链</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>
<div id="header" nz-row [class.containBg]="size =='large'">
<div id="header-logo-title" nz-col nzSpan="6">
<img src="https://56462271.oss-cn-beijing.aliyuncs.com/web/logo.jpg"
alt="logo" id="logo">
<span id="header-title"><a routerLink="/">小海博客</a></span>
</div>
<ul id="nav" nz-col nzSpan="12" [ngStyle]="{'display':showList?'block':'none'}">
<ng-template *ngFor="let item of pageList" [ngIf]="item.show">
<li (click)="dealLink(item.path)" style="user-select: none;cursor: pointer">
<i nz-icon [nzType]="item.icon" [nzTheme]="item.iconType"></i>
{{item.name}}
</li>
</ng-template>
</ul>
<div id="header-user-login" *ngIf="!userInfo">
<button nz-button nzType="primary" (click)="login()">登录</button>
<button nz-button nzType="primary" (click)="registration()">注册</button>
</div>
<div *ngIf="userInfo" id="info">
<img [src]="userInfo.avatarImgUrl" alt="avatar" id="avatar">
<button nz-button nzType="link" class="info-name"
nz-dropdown [nzDropdownMenu]="menu" nzPlacement="bottomRight" nzTrigger="click">
{{userInfo.displayName}}<i nz-icon nzType="caret-down" nzTheme="outline"></i>
</button>
<nz-dropdown-menu #menu="nzDropdownMenu">
<ul nz-menu nzSelectable>
<li nz-menu-item (click)="toAdminPage()"><i nz-icon nzType="info-circle" nzTheme="outline"></i>管理后台</li>
<hr style="opacity: 0.5">
<li nz-menu-item (click)="logout()"><i nz-icon nzType="logout" nzTheme="outline"></i>注销登录</li>
</ul>
</nz-dropdown-menu>
</div>
<button id="header-menu" nz-button [style.top.px]="size=='large'?25:10" (click)="changeMenuStatus()">
<i nz-icon [nzType]="showList?'arrow-left':'menu'" nzTheme="outline"></i>
</button>
</div>

View File

@@ -0,0 +1,160 @@
@import "../../global-variables";
.containBg {
background-image: url("src/assets/img/bg_header.jpg") !important;
color: #ffffff !important;
min-height: @header-min-height;
background-position: center center !important;
background-size: cover !important;
opacity: 1 !important;
padding-top: 15px !important;
.info-name {
color: white !important;
}
a {
color: white !important;
}
}
#header {
// 设置header背景
background: rgba(255, 255, 255, 0.55);
color: black;
font-family: -apple-system, BlinkMacSystemFont, opensans, Optima, "Microsoft Yahei", sans-serif;
padding-top: 0px;
position: relative;
left: 0;
right: 0;
z-index: 1000;
div {
display: inline-block;
}
#avatar {
width: 30px;
height: 30px;
border-radius: 50%;
border: 1px solid #bdbdbd;
}
#logo {
// 设置logo的宽高
width: @header-logo-width;
height: @header-logo-height;
border-radius: 50%;
//position: relative;
//top: 0;
//left: 30px;
}
#header-title {
//position: relative;
//top: 0;
//left: @logo_wh;
font-size: xx-large;
font-weight: lighter;
min-width: 130px;
max-width: 50%;
vertical-align: middle;
a {
color: black;
}
}
#header-user-login, #info {
float: right;
margin-right: 15px;
line-height: 50px;
[nz-button] {
margin-right: 30px;
}
}
.info-name {
color: black;
margin-right: 0 !important;
}
#header-menu {
display: none;
}
#nav {
@item-width: 80px;
width: auto;
list-style: none;
li:hover {
background: #ececec;
//color: black;
}
li {
float: left;
height: 50px;
width: @item-width;
line-height: 50px;
//color: white;
text-align: center;
}
}
}
/* max-width:910px */
@media screen and (max-width: @max-width) {
#header {
#header-logo-title {
position: static;
width: 75%;
}
#nav {
display: none;
background: white;
opacity: 0.5;
position: relative;
left: 0;
top: 0;
width: 100%;
li {
clear: both;
width: 100%;
line-height: 40px;
height: 40px;
text-align: center;
color: black;
}
li:hover {
background: #bce672;
}
}
#header-user-login {
display: none;
}
#header-menu {
display: block !important;
position: absolute;
right: 30px;
top: 25px
}
#info {
display: block !important;
position: absolute;
right: 40px;
top: 15px
}
}
}

View File

@@ -1,25 +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();
});
});
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();
});
});

View File

@@ -1,28 +1,149 @@
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();
}
}
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {windowWidthChange} from '../../utils/util';
import {NavigationEnd, Router, RouterEvent} from '@angular/router';
import {filter} from 'rxjs/operators';
import {ApiService} from '../../api/api.service';
import {User} from '../../class/User';
import {LocalStorageService} from '../../utils/local-storage.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.less']
})
export class HeaderComponent implements OnInit {
constructor(private router: Router,
private apiService: ApiService) {
this.pageList = [
{name: '首页', path: '/', icon: 'home', iconType: 'fill', show: true},
{name: '分类', path: '/categories', icon: 'project', iconType: 'fill', show: true},
{name: '标签', path: '/tags', icon: 'tags', iconType: 'fill', show: true},
// {name: '留言', path: '/leaveMsg', icon: 'carry-out', iconType: 'fill', show: true},
{name: '更新', path: '/update', icon: 'up-square', iconType: 'fill', show: true},
{name: '友链', path: '/links', icon: 'link', iconType: 'outline', show: true},
{name: '登录', path: '/login', icon: 'login', iconType: 'outline', show: false},
{name: '注册', path: '/registration', icon: 'user', iconType: 'outline', show: false}
];
this.showList = window.innerWidth > this.mobileMaxWidth;
this.changeLoginButtonV();
// 监听宽度变化
windowWidthChange(() => {
this.showList = window.innerWidth > this.mobileMaxWidth;
this.changeLoginButtonV();
});
this.router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe((e: RouterEvent) => {
// indexOf ==> -1/index
const indexOfParam = e.url.indexOf('?');
const path = e.url.substr(0, indexOfParam === -1 ? e.url.length : indexOfParam);
// lastIndexOf ==> 0/index
const indexOf = path.lastIndexOf('/');
const prefix = path.substr(0, indexOf === 0 ? path.length : indexOf);
this.currentPath = prefix;
if (prefix === '/user' || prefix === '/write' || prefix === '/update') {
this.size = 'default';
} else {
this.size = 'large';
}
this.getInfo();
});
}
@Output() loginEvent = new EventEmitter();
@Output() registrationEvent = new EventEmitter();
size: 'large' | 'default';
currentPath: string;
public pageList: {
path: string;
name: string;
icon: string;
iconType: 'outline' | 'fill' | 'twotone';
show: boolean;
}[];
public showList = true;
// css 样式中设置移动端最大宽度为910px 见src/app/global-variables.less
private readonly mobileMaxWidth = 940;
@Input() userInfo: User;
ngOnInit() {
}
changeMenuStatus() {
this.showList = !this.showList;
this.changeLoginButtonV();
}
private changeLoginButtonV() {
this.pageList.forEach(e => {
if (e.name === '登录' || e.name === '注册') {
if (this.userInfo) {
e.show = false;
} else {
e.show = (this.showList && window.innerWidth < this.mobileMaxWidth);
}
}
});
}
dealLink(path: string) {
this.showList = window.innerWidth > this.mobileMaxWidth;
if (path === '/login') {
this.login();
} else if (path === '/registration') {
this.registration();
} else {
this.router.navigateByUrl(path);
}
}
login() {
this.showList = window.innerWidth > this.mobileMaxWidth;
if (this.currentPath === '/article' || this.currentPath === '/write') {
this.loginEvent.emit();
return;
}
this.router.navigateByUrl('/user/login');
}
registration() {
this.showList = window.innerWidth > this.mobileMaxWidth;
if (this.currentPath === '/article' || this.currentPath === '/write') {
this.registrationEvent.emit();
return;
}
this.router.navigateByUrl('/user/registration');
}
getInfo() {
this.apiService.userInfo().subscribe(data => {
this.userInfo = data.result;
this.changeLoginButtonV();
},
error => {
}
);
}
logout() {
this.apiService.logout().subscribe(data => {
location.reload();
},
error => {
}
);
this.userInfo = null;
}
toAdminPage() {
window.location.href = '/admin';
}
}

View File

@@ -0,0 +1,13 @@
// 定义less 中的全局变量
// 使用的时候导入本文件
/** 移动端适配时的分割宽度 */
@max-width: 940px;
/**** header ****/
// header的最小高度
@header-min-height: 300px;
@header-logo-width: 50px;
@header-logo-height: 50px;
/**** header ****/

View File

@@ -1,330 +0,0 @@
/* 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;
}
}

View File

@@ -1,149 +0,0 @@
<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>&nbsp;&nbsp;上一篇文章:{{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>&nbsp;&nbsp;下一篇文章篇文章:{{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>

View File

@@ -1,228 +0,0 @@
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;
}
}
}

View File

@@ -1,65 +0,0 @@
#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%;
}
}

View File

@@ -1,25 +0,0 @@
<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>

View File

@@ -1,71 +0,0 @@
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;
}
});
}
}

View File

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

View File

@@ -1,58 +0,0 @@
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;
}
});
}
}

View File

@@ -1,180 +0,0 @@
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;
}
}

View File

@@ -1,115 +0,0 @@
<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>&nbsp;
<a [routerLink]="['/tag']" [queryParams]="{name:item}">{{item}}</a>&nbsp;&nbsp;
</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>

View File

@@ -1,76 +0,0 @@
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);
}
}

View File

@@ -1,53 +0,0 @@
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%;
}
}

View File

@@ -1,82 +0,0 @@
<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>

View File

@@ -1,107 +0,0 @@
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;
}
});
}
}

View File

@@ -1,51 +0,0 @@
.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;
}
}

View File

@@ -1,34 +0,0 @@
<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>

View File

@@ -1,94 +0,0 @@
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);
}
});
}
}

View File

@@ -1,87 +0,0 @@
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;
}
}

View File

@@ -1,28 +0,0 @@
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() {
}
}

View File

@@ -1,50 +0,0 @@
<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>

View File

@@ -1,65 +0,0 @@
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;
}
}

View File

@@ -1,93 +0,0 @@
.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%;
}
}

View File

@@ -1,56 +0,0 @@
<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>

View File

@@ -1,92 +0,0 @@
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();
}
}
}

View File

@@ -1,71 +0,0 @@
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);
}
});
}
}

View File

@@ -1,65 +0,0 @@
#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%;
}
}

View File

@@ -1,25 +0,0 @@
<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>

View File

@@ -1,62 +0,0 @@
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;
});
}
}

View File

@@ -1,96 +0,0 @@
@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;
}

View File

@@ -1,56 +0,0 @@
<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>

View File

@@ -1,28 +0,0 @@
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();
}
}
}

View File

@@ -1,30 +0,0 @@
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());
});
}
}

View File

@@ -1,133 +0,0 @@
/* @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;
}

View File

@@ -1,62 +0,0 @@
<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>

View File

@@ -1,183 +0,0 @@
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('文章不存在');
}
});
}
}

View File

@@ -1,12 +0,0 @@
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();
});
});

View File

@@ -1,97 +0,0 @@
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;
}
}

View File

@@ -1,12 +0,0 @@
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();
});
});

View File

@@ -1,27 +0,0 @@
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;
}
}

View File

@@ -1,12 +0,0 @@
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();
});
});

View File

@@ -1,124 +0,0 @@
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);
}
}

View File

@@ -1,24 +0,0 @@
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;
}
}

View File

@@ -1,12 +0,0 @@
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();
});
});

View File

@@ -1,110 +0,0 @@
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 = ''
}
}

View File

@@ -1,12 +0,0 @@
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();
});
});

View File

@@ -1,28 +0,0 @@
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;
}
});
}
}

View File

@@ -1,12 +0,0 @@
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();
});
});

View File

@@ -1,26 +0,0 @@
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;
}
}

View File

@@ -1,12 +0,0 @@
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();
});
});

View File

@@ -1,34 +0,0 @@
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;
}
});
}
}

View File

@@ -1,12 +0,0 @@
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();
});
});

View File

@@ -1,100 +0,0 @@
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);
}
}

View File

@@ -0,0 +1,9 @@
export const ColorList: { bgColor: string, fontColor: string }[] = [
{bgColor: '#7bcfa6', fontColor: '#000000'}, // 石青
{bgColor: '#bce672', fontColor: '#000000'}, // 松花色
{bgColor: '#ff8936', fontColor: '#000000'}, // 橘黄
{bgColor: '#f0c239', fontColor: '#000000'}, // 缃色
{bgColor: '#808080', fontColor: '#ffffff'}, // 灰色
{bgColor: '#3eede7', fontColor: '#000000'}, // 碧蓝
{bgColor: '#177cb0', fontColor: '#ffffff'}, // 靛青
];

View File

@@ -1,12 +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();
});
});
import { TestBed } from '@angular/core/testing';
import { LocalStorageService } from './local-storage.service';
describe('LocalStorageService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: LocalStorageService = TestBed.get(LocalStorageService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,63 @@
import {Injectable} from '@angular/core';
import {User} from '../class/User';
@Injectable({
providedIn: 'root'
})
export class LocalStorageService {
constructor() {
}
// 1分钟
readonly place = 60 * 1000;
getToken(): string {
return localStorage.getItem('token');
}
setToken(token: string) {
localStorage.setItem('t', new Date().valueOf().toString());
localStorage.setItem('token', token);
}
removeToken() {
localStorage.removeItem('token');
}
isLogin() {
return this.getToken() != null;
}
setUser(user: User) {
// TODO: 简单加个密
localStorage.setItem('t', new Date().valueOf().toString());
return localStorage.setItem('user', JSON.stringify(user));
}
getUser(): User {
if (!this.checkNeedNet()) {
return JSON.parse(localStorage.getItem('user'));
}
}
removeUser() {
return localStorage.removeItem('user');
}
clear() {
localStorage.removeItem('token');
localStorage.removeItem('user');
return localStorage.removeItem('t');
}
checkNeedNet() {
const t: number = Number.parseInt(localStorage.getItem('t'), 10);
if (isNaN(t) || new Date().valueOf() - t > this.place) {
localStorage.removeItem('t');
localStorage.removeItem('user');
return true;
}
return false;
}
}

View File

@@ -0,0 +1,42 @@
import {environment} from '../../environments/environment';
export class Logger {
private static inited = false;
static info(obj: object) {
this.printLogInfo();
if (environment.logger) {
// tslint:disable-next-line:no-console
console.info(obj);
}
}
static debug(obj: object) {
this.printLogInfo();
if (environment.logger) {
// tslint:disable-next-line:no-console
console.debug(obj);
}
}
static error(obj: object) {
this.printLogInfo();
if (environment.logger) {
// tslint:disable-next-line:no-console
console.error(obj);
}
}
static printLogInfo() {
const option = {
environment: environment.production ? 'release' : 'debug',
};
if (!this.inited) {
// tslint:disable-next-line:no-console
console.info(option);
this.inited = true;
}
}
}

View File

@@ -0,0 +1,11 @@
export class SvgIconUtil {
constructor() {
}
// tslint:disable-next-line:max-line-length
static readonly nameIcon = '<svg t="1581933298087" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3223" width="200" height="200"><path d="M983.04 163.84H40.96c-24.576 0-40.96 16.384-40.96 40.96v614.4c0 20.48 16.384 40.96 40.96 40.96h942.08c20.48 0 40.96-20.48 40.96-40.96V204.8c0-24.576-20.48-40.96-40.96-40.96zM253.952 749.568c0-102.4 61.44-192.512 147.456-233.472-28.672-24.576-45.056-61.44-45.056-102.4 0-77.824 65.536-143.36 143.36-143.36s143.36 65.536 143.36 143.36c0 40.96-12.288 73.728-36.864 98.304 94.208 36.864 163.84 131.072 163.84 237.568H253.952z" p-id="3224" fill="#1296db"></path></svg>';
// tslint:disable-next-line:max-line-length
static readonly locationIcon = '<svg t="1581933583276" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3981" width="200" height="200"><path d="M511.932268 0c-213.943865 0-387.880498 170.933833-387.880499 381.06211 0 164.815346 238.869364 485.098975 341.66447 620.247558a58.249807 58.249807 0 0 0 46.216029 22.690332c18.129688 0 35.491743-8.443964 46.328916-22.690332 102.704796-135.126006 341.709624-455.432213 341.709624-620.247558C899.970808 170.933833 725.89871 0 511.932268 0z m0 519.574733c-91.393496 0-165.786176-72.902569-165.786176-162.670489 0-89.722765 74.39268-162.738221 165.786176-162.73822 91.438651 0 165.718443 73.015456 165.718443 162.73822 0 89.76792-74.279793 162.670488-165.718443 162.670489z" fill="#1296db" p-id="3982"></path></svg>';
}

View File

@@ -0,0 +1,17 @@
class DataProperties {
static windowsMode: 'small' | 'middle' | 'large' = null;
}
export function windowWidthChange(func) {
DataProperties.windowsMode = window.innerWidth < 910 ? 'small' : (window.innerWidth > 1300 ? 'large' : 'middle');
// 监听宽度变化
window.addEventListener('resize', () => {
DataProperties.windowsMode = window.innerWidth < 910 ? 'small' : (window.innerWidth > 1300 ? 'large' : 'middle');
func();
});
}
export function windowsMode() {
return DataProperties.windowsMode;
}

View File

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

View File

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

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