从"Blog"仓库中分离出来

This commit is contained in:
小海
2019-11-28 19:26:45 +08:00
commit c2aaf280db
616 changed files with 104128 additions and 0 deletions

View File

@@ -0,0 +1,330 @@
/* markdown Css style */
* {
margin: 0;
padding: 0;
}
.list-group li {
/* margin-top: 10px;*/
list-style: none;
}
h2 {
margin: 0;
}
hr {
margin: 10px 0;
}
.arUl {
text-align: center;
margin: 0;
padding: 0;
}
.arUl li {
margin-right: 12px;
display: inline;
}
.arUl li i {
margin-right: 5px;
}
.arTypeOriginal {
background: #5eb95e;
}
.arTypeReprint {
background: #F37B1D;
}
.arType {
color: #ffffff;
font-size: 0.8em;
padding: 2px 6px;
border-radius: 5px;
}
.article-titlea {
font-size: 150%;
}
.article-meta {
width: 100%;
float: left;
}
.article-meta a {
color: #666666;
}
.article-road {
margin-left: 2%;
}
.secd {
margin-left: 30%;
}
#title {
word-break: break-all;
border: none;
padding: 0;
font-size: 2em;
margin: 0;
}
.article-text {
word-break: break-all
}
.article-tag {
margin-left: 5%;
}
/*文章底部的tag标签*/
.tag {
color: #666666;
}
.article-bAnda {
width: 90%;
margin-top: 2%;
margin-left: 5%;
margin-right: 5%;
}
.articlePublishDate,
.originalAuthor,
.categories {
display: inline-block;
}
.article-input-textarea {
border-color: #aaaaaa;
border-width: 2px;
border-radius: 10px;
width: 100%;
height: 100px;
max-height: 700px;
padding: 5px;
}
.comment-submit {
float: right;
color: white;
background: #3bb4f2;
border: none;
border-radius: 5px;
padding: 3px 8px;
margin-top: 20px;
}
.comment-list {
margin: 50px 10% 20px;
}
.comment-input {
border-color: #ececec;
border-width: 1px;
outline: none;
border-radius: 4px;
margin-left: 10px;
margin-top: 21px;
padding-left: 5px;
}
.article-comment {
margin: 0 10%;
}
.comment-input-coverText {
text-align: center;
background-color: white;
margin: 5px;
width: 100%;
height: 100%;
}
.input-area {
padding-top: 30px;
}
.buttonDisable {
pointer-events: none
}
.articleButton {
background: none;
border: none;
user-select: none;
}
.articleButton:hover {
cursor: pointer;
}
.commentUser {
background: #5eb95e;
padding: 3px 8px;
border-radius: 3px;
color: white
}
.commentItem {
margin-top: 7px;
}
.loading {
position: fixed;
height: 100%;
width: 100%;
left: 0;
top: 0;
background: #fff;
}
.loading img {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
}
#pagination {
text-align: center
}
.tag {
margin-right: 10px;
}
.tag i {
margin-right: 5px;
}
.article-last,
.article-next {
display: block;
}
#over {
display: block;
width: 80px;
height: 80px;
border-radius: 50%;
line-height: 80px;
margin: 15px auto;
text-align: center;
font-weight: bold;
font-size: 2em;
background: #3bb4f2;
color: white
}
#copyright {
width: 90%;
margin: 15px 5%;
border-left: 5px solid #FF1700;
padding: 10px;
background: #ececec
}
p {
margin-bottom: 0
}
.comment {
border: 1px solid #cccccc;
border-radius: 5px;
padding: 5px 10px;
}
#content {
overflow-y: hidden;
}
#content p {
word-break: break-all
}
button {
padding-left: 8px;
padding-right: 8px;
}
textarea {
padding: 8px;
}
#content {
padding: 0 5%;
}
.noToc{
width: 65% !important;
margin: 0 auto !important;
height: auto;
position: static !important;
border-radius: 10px;
background-color: #fff;
}
@media only screen and (min-width: 768px) {
.article-content {
left: 400px;
width: auto;
height: auto;
margin-right: 500px;
position: relative;
border-radius: 10px;
background-color: #fff;
}
.article-toc-main{
border-radius: 10px;
background: #fff;
position: fixed;
left: 40px;
top: 50%;
transform: translateY(-50%);
width: 320px;
padding: 10px;
}
#article-toc{
max-height: 700px;
overflow-y: scroll;
}
}
@media only screen and (max-width: 768px) {
.article-content {
width: 100%;
margin-top: -80px;
padding-top: 60px;
margin-right:0;
border-radius: 0;
margin-left: 0!important;
}
#content {
padding: 0 8px;
}
.article-toc-main{
position:relative;
padding: 10px;
background: #fff;
}
.noToc{
width: 100% !important;
}
}

View File

@@ -0,0 +1,149 @@
<ng-template [ngIf]="article">
<nz-spin [nzSpinning]="!loadOk">
<div class="article-content am-article" [ngClass]="{'noToc':!showToc}">
<div style="text-align: center;word-break: break-all">
<h1 class="article-title" id="title">{{article.title}}</h1>
</div>
<br>
<ul class="arUl">
<li>
<span class="arType"
[ngClass]="{'arTypeOriginal': article.original,'arTypeReprint':!article.original}">{{article.original ? "原创" : "转载"}}</span>
</li>
<li><i nz-icon nzType="calendar" nzTheme="outline"></i>{{article.publishDateFormat}}</li>
<li><i nz-icon nzType="user" nzTheme="outline"></i>{{article.authorName}}</li>
<li><i nz-icon nzType="folder" nzTheme="outline"></i>
<a [routerLink]="['/category']" [queryParams]="{'name':article.category}">
<span>{{article.category}}</span></a>
</li>
</ul>
<hr/>
<div class="article-text am-text-break am-article-bd">
<!-- <div id="content" [innerHTML]="article.htmlContent">
</div> -->
<div class="article-toc-main" *ngIf="showToc">
<!-- fixme 不同文章切换时 无法生效 -->
<span><strong>目录:</strong></span><br>
<div id="article-toc" class="markdown-body editormd-preview-container">
</div>
</div>
<div id="content">
<textarea></textarea>
</div>
<span id="over">over</span>
</div>
<!-- 文章版权 -->
<div id="copyright">
<p>本文作者:{{article.authorName}} </p>
<p>{{article.original ? "本文" : "原文"}}链接:{{article.original ? copyRightUrl : article.url}}</p>
<p>版权声明:转载请注明出处</p>
</div>
<div class="article-tag" id="tag">
<!-- TODO -->
<span *ngFor="let item of (article.tags||'')" class="tag">
<i nz-icon nzType="tag" nzTheme="fill"></i>
<a class="tag" [routerLink]="['/tag']" [queryParams]="{name:item}">{{item}}</a>
</span>
</div>
<hr/>
<div class="article-bAnda">
<span class="article-last">
<button (click)="toArticle(article.nextArticleId)" class="articleButton"
[ngClass]="{'buttonDisable': article.nextArticleId==-1}">
<i nz-icon nzType="caret-up"
nzTheme="outline"></i>&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

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

View File

@@ -0,0 +1,228 @@
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Router} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {Location} from '@angular/common';
import {NzMessageService} from 'ng-zorro-antd';
import {ArticleService} from '../../services/article/article.service';
import {CommentService} from '../../services/comment/comment.service';
import {UserService} from '../../services/user/user.service';
import {CommentReq} from '../../class/commentReq';
import {Article} from '../../class/article';
declare var editormd;
declare var $;
@Component({
selector: 'app-article',
templateUrl: './article.component.html',
styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
constructor(private articleService: ArticleService,
private routerinfo: ActivatedRoute,
private router: Router,
private message: NzMessageService,
private titleService: Title,
private location: Location,
public commentService: CommentService,
public userService: UserService) {
}
copyRightUrl: string;
public pageNum: number = 1;
public pageSize: number = 10;
public articleId: number;
public article: Article = new Article();
public comment4submit: string = '';
loadOk: boolean = false; // 文章的加载
loading: boolean = true; // 评论的加载
relyIndex: number;
public responseComment: CommentReq;
public showToc: boolean = true;
/**
* editor.md preview
*
*/
public preview(markdownContent) {
editormd.markdownToHTML('content', {
markdown: markdownContent,
// htmlDecode : true, // 开启 HTML 标签解析,为了安全性,默认不开启
htmlDecode: 'style,script,iframe', // you can filter tags decode
toc : true,
tocm : true, // Using [TOCM]
tocContainer : '#article-toc', // 自定义 ToC 容器层
// gfm : false,
tocDropdown: false,
// markdownSourceCode : true, // 是否保留 Markdown 源码,即是否删除保存源码的 Textarea 标签
emoji: true,
taskList: true,
// tex : true, //科学公式 默认不解析
flowChart: true, // 默认不解析
sequenceDiagram: true, // 默认不解析
});
this.checkALink();
}
ngOnInit() {
this.articleId = +this.routerinfo.snapshot.paramMap.get('id');
this.responseComment = new CommentReq(true);
this.getArticle();
this.getPageComment();
this.responseComment.articleID = this.articleId;
this.copyRightUrl = location.href;
}
/**
* 获取并处理文章
*/
getArticle() {
this.articleService.getArticleById(this.articleId).subscribe(data => {
if (data.code === 0) {
// 清空远先的markdown 内容
document.getElementById('content').innerHTML = '';
this.article = data.result;
this.preview(data.result.mdContent);
window.scrollTo(0, 0);
this.titleService.setTitle(data.result.title);
// 修改url栏的地址
this.location.replaceState('article/' + data.result.id);
this.copyRightUrl = location.href;
this.loadOk = true;
} else if (data.code === 201) {
// 文章不存在
this.router.navigateByUrl('404');
}
});
}
/**
* 跳转id文章
* @param id 文章id
*/
toArticle(id: number) {
if (id == null || id <= 0) {
return;
}
this.articleId = id;
this.loadOk = false;
this.getArticle();
this.getPageComment();
}
/**
* 评论调到某一页
* @param a 页码数
*/
toPage(a: number) {
if (a === this.pageNum) {
return;
}
this.pageNum = a;
this.getPageComment();
}
// 评论
getPageComment() {
this.commentService.getPageComment(this.articleId, this.pageNum, this.pageSize).subscribe(data => {
if (data.code === 0) {
this.loading = false;
this.commentService.getResponseComment();
}
});
}
/**
* 一级评论的创建
*/
submitComment() {
if (this.comment4submit == null || this.comment4submit === '') {
this.message.info('内容不能为空');
return;
}
const submitBody: CommentReq = new CommentReq(true);
submitBody.content = this.comment4submit;
submitBody.articleID = this.articleId;
submitBody.pid = -1;
this.commentService.submitComment(submitBody);
this.comment4submit = null;
}
/**
* 创建文本输入框的过程
* @param id 父评论的id
* @param name 富评论的作者名
* @param index 索引 便于显示输入框
*/
replyTo(id, name, index) {
if (!this.userService.userInfo) {
this.message.info('请先登录哟~~~');
return;
}
this.responseComment.pid = id;
this.responseComment.content = ' @' + name + ' ';
this.relyIndex = index;
}
/**
* 回复的数据提交
*/
reply() {
if (this.responseComment.content == null || this.responseComment.content === '') {
this.message.info('内容不能为空');
return;
}
if (this.responseComment.pid == null) {
this.message.error('非法操作');
}
this.commentService.rely(this.responseComment).subscribe(data => {
if (data.code === 0) {
if (this.commentService.commentPage.list[this.relyIndex].child == null) {
this.commentService.commentPage.list[this.relyIndex].child = [];
}
this.commentService.commentPage.list[this.relyIndex].child.unshift(data.result);
this.responseComment.content = null;
}
});
}
// 曲线救国 重新设置a的href值
checkALink() {
const as = document.getElementsByTagName('a');
let hrefStr: string = null ;
let count: number = 0;
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < as.length; i++) {
hrefStr = decodeURI(as[i].href);
// console.log(as[i].getAttribute('level'));
if (as[i].getAttribute('level') != null) {
// 截取锚点名称
const anchorName: string = hrefStr.substr(hrefStr.indexOf('#'), hrefStr.length);
// 重新获取url 并拼接锚点名称
as[i].href = location.origin + location.pathname + anchorName;
count++;
}
}
if (count === 0) {
this.showToc = false;
}
}
}

View File

@@ -0,0 +1,65 @@
#main{
width:60%;
margin:0 auto;
border-radius: 10px;
padding: 10px;
background: #ffffff;
}
#category{
float: left;
width: 100%;
padding: 10px;
border: 1px solid #ececec;
box-shadow: 5px 5px 5px #888888;
margin-bottom: 20px;
}
#detail{
clear: both;
margin-top: 40px;
}
.singleTag{
background: #ffffff;
border-radius: 5px;
line-height: 15px;
margin: 5px 10px;
float: left;
cursor: pointer;
border: 1px solid #000000;
padding: 5px 8px;
}
ul{
margin: 0;
list-style: none;
}
.art{
padding: 8px;
border: 1px solid #ececec;
box-shadow: 5px 5px 5px #aaaaaa;
margin: 20px 0 ;
}
.title:hover{
animation: 1s move ;
}
@keyframes move{
from{
margin-left: 0;
}
to{
margin-left: 20px;
}
}
.active{
background: #6DAB90;
}
@media only screen and (max-width:768px){
#main{
width: 100%;
}
}

View File

@@ -0,0 +1,25 @@
<div id="main">
<div id="category">
<ul *ngIf="categoryService.categories">
<li *ngFor="let category of categoryService.categories" (click)="changeCategory(category.name)">
<span class="singleTag" [ngClass]="{active:currentCategoryName==category.name}"
[nzTitle]="'此分类有'+(category.articles.split(',').length-1)+'篇文章'" nzPlacement="topCenter" nz-tooltip>
<i nz-icon nzType="folder" nzTheme="fill"></i> {{category.name}}</span></li>
</ul>
<h2 *ngIf="!categoryService.categories">暂时没有分类</h2>
</div>
<ul id="detail" *ngIf="currentArticleList">
<li *ngFor="let article of currentArticleList.list">
<div class="art">
<h2><a [routerLink]="'/article/'+article.id" class="title">{{article.title}}</a></h2>
<hr>
<div> {{article.summary}}</div>
</div>
</li>
</ul>
<div *ngIf="!currentArticleList">
<h2 style="text-align: center">该分类暂无文章</h2>
</div>
</div>

View File

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

View File

@@ -0,0 +1,71 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {Location} from '@angular/common';
import {CategoryService} from '../../services/category/category.service';
import {ArticleService} from '../../services/article/article.service';
import {Page} from '../../class/page';
import {Article} from '../../class/article';
@Component({
selector: 'app-categories',
templateUrl: './category.component.html',
styleUrls: ['./category.component.css']
})
export class CategoryComponent implements OnInit {
constructor(private routerinfo: ActivatedRoute,
private titleService: Title,
private location: Location,
public categoryService: CategoryService,
public articleService: ArticleService) {
titleService.setTitle('小海博客|分类');
}
// 当前timeliness展示的分类
public currentCategoryName: string;
public currentArticleList: Page<Article>;
ngOnInit() {
this.currentCategoryName = this.routerinfo.snapshot.queryParams.name;
if (this.categoryService.categories == null) {
this.categoryService.getAllCategory().subscribe((data) => {
// 有分类数据
if (data.code === 0 && data.result.length > 0) {
// 是否更具url的参数获取数据
if (this.currentCategoryName == null) {
this.currentCategoryName = data.result[0].name;
}
this.getArticle();
}
});
} else if (this.currentCategoryName == null) {
this.currentCategoryName = this.categoryService.categories[0].name;
}
this.getArticle();
}
/**
* 切换 分类
* @param name 分类名称
*/
changeCategory(name: string) {
if (this.currentCategoryName === name) {
return;
}
this.currentCategoryName = name;
this.getArticle();
this.location.replaceState('category', '?name=' + name);
}
getArticle() {
this.articleService.getArticleByCategory(this.currentCategoryName).subscribe(data => {
if (data.code === 0) {
this.currentArticleList = data.result;
}
});
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,58 @@
import {Component, OnInit} from '@angular/core';
import {Title} from '@angular/platform-browser';
import {Router, ActivatedRoute} from '@angular/router';
import {UserService} from '../../services/user/user.service';
@Component({
selector: 'app-email-verify',
templateUrl: './email-verify.component.html',
styleUrls: ['./email-verify.component.css']
})
export class EmailVerifyComponent implements OnInit {
constructor(private titleService: Title,
public userService: UserService,
private router: Router,
public routerinfo: ActivatedRoute) {
titleService.setTitle('小海博客|邮箱验证');
}
type: string = 'info';
message: string = '正在验证,请稍等';
desc: string = '';
private email: string;
private verifyId: string;
ngOnInit() {
this.email = this.routerinfo.snapshot.queryParams.email;
this.verifyId = this.routerinfo.snapshot.queryParams.verifyId;
if (this.email == null || this.verifyId == null) {
this.type = 'warning';
this.message = '数据不全';
this.desc = '链接可能被修改了,请重新点击邮箱中的链接,或者重新发送邮件';
}
const reqBody = {
email: this.email,
verifyId: this.verifyId
};
this.userService.emailVerify(reqBody).subscribe(data => {
if (data.code === 0) {
this.type = 'success';
this.message = '验证成功';
// this.desc = "5秒后转跳到后台"
// setTimeout(() => {
// window.location.href = "admin"
// }, 5000);
} else {
this.type = 'error';
this.message = data.msg;
}
});
}
}

View File

@@ -0,0 +1,180 @@
hr {
margin: 0;
}
.title {
text-align: center;
margin: 0;
display: block;
}
.content {
margin-top: 10px;
margin-left: 10px;
margin-right: 10px;
}
h2 {
margin: 10px;
}
.main button {
width: 45px;
height: 45px;
margin-right: 15px;
margin-left: 15px;
margin-top: 10px;
background: none;
border: 1px solid #000000;
border-radius: 50%;
}
.contact {
width: 120px;
height: 120px;
margin-top: 15px;
}
/* info */
.main {
background-color: #ffffff;
border-radius: 10px;
width: 350px;
margin-bottom: 50px;
padding-bottom: 10px;
word-break: break-all
}
span {
margin: 0;
}
.countInfo i {
margin-right: 5px;
}
.countInfo li{
list-style: none;
}
/* article */
.article {
min-height: 200px;
background-color: #ffffff;
border-radius: 10px;
margin-bottom: 40px;
}
.aUl {
margin: 20px 0 0 0;
padding: 0;
}
.aUl li {
margin-right: 12px;
display: inline;
}
.aUl li i {
margin-right: 5px;
}
.article .aTitle {
margin: 0;
display: inline;
}
/** todo : 动画效果 */
.aTitle:hover {
text-decoration: underline;
}
.article .aType {
color: #ffffff;
font-size: 0.8em;
padding: 2px 6px;
border-radius: 5px;
}
.aTypeOriginal {
background: #5eb95e;
}
.aTypeReprint {
background: #F37B1D;
}
.aSummary {
width: 98%;
padding-top: 10px;
font-size: 1.1em;
line-height: 2em;
color: #666666;
margin-bottom: 10px;
}
/* 自动换行 */
.aTitle, .aSummary {
word-break: break-all;
}
.tags {
margin-top: 10px;
font-size: 1.1em;
}
.about-contact-icon:hover {
cursor: pointer;
}
#pagination {
text-align: center;
}
@media only screen and (min-width: 768px) {
.left {
width: 380px;
height: auto;
flex-grow: 0;
border-radius: 10px;
margin-left: 80px;
padding-bottom: 80px;
direction: ltr;
}
.right {
height: auto;
max-width: 1000px;
flex-grow: 1;
margin-left: 80px;
margin-right: 80px;
direction: ltr;
}
.container {
display: flex;
align-items: flex-start;
direction: rtl
}
}
@media only screen and ( max-width: 768px ) {
#bloger {
display: none;
}
.article {
margin: 0 10px 30px 10px;
}
.main {
width: 94%;
margin-left: 3%;
}
.left {
margin-top: 30px;
}
}

View File

@@ -0,0 +1,115 @@
<div class="container">
<h2 *ngIf="!articleService.currentPage" style="width: 100%;text-align: center">.暂时还未发布文章.</h2>
<div class="right" *ngIf="articleService.currentPage">
<nz-card class="article am-animation-slide-right" *ngFor="let article of articleService.currentPage.list"
[nzLoading]="!articleService.currentPage">
<a [routerLink]="'/article/'+article.id"><h2 class="aTitle">{{article.title}}</h2></a>
<ul class="aUl">
<li>
<span class="aType" [ngClass]="{'aTypeOriginal': article.original,'aTypeReprint':!article.original}">
{{article.original ? "原创" : "转载"}}
</span>
</li>
<li><i nz-icon nzType="calendar" nzTheme="outline"></i>{{article.publishDateFormat}}</li>
<li><i nz-icon nzType="user" nzTheme="outline"></i>{{article.authorName}}</li>
<li>
<i nz-icon nzType="folder" nzTheme="outline"></i>
<a [routerLink]="['/category']" [queryParams]="{'name':article.category}">{{article.category}}</a>
</li>
</ul>
<div class="aSummary">
<p>{{article.summary}}</p>
</div>
<a [routerLink]="'/article/'+article.id" style="font-size: 1.2em;">
阅读全文
<i nz-icon nzType="double-right" nzTheme="outline"></i>
</a>
<hr>
<div class="tags">
<span *ngFor="let item of article.tags">
<i nz-icon nzType="tag" nzTheme="outline"></i>&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

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

View File

@@ -0,0 +1,76 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {Location} from '@angular/common';
import {TagService} from '../../services/tag/tag.service';
import {CountService} from '../../services/count/count.service';
import {ArticleService} from '../../services/article/article.service';
import {WebUpdateService} from '../../services/update/web-update.service';
@Component({
selector: 'app-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.css']
})
export class IndexComponent implements OnInit {
constructor(public tagService: TagService,
public countService: CountService,
public articleService: ArticleService,
public webUpdateService: WebUpdateService,
private routerinfo: ActivatedRoute, private title: Title,
private location: Location) {
title.setTitle('小海博客');
}
public pageNum: number;
public pageSize: number;
imagePath: string;
ngOnInit() {
this.pageNum = this.routerinfo.snapshot.queryParams.page;
this.pageSize = this.routerinfo.snapshot.queryParams.count;
// 数据合法性验证
this.pageNum = (this.pageNum == null || this.pageNum < 1) ? 1 : this.pageNum;
this.pageSize = (this.pageSize == null || this.pageSize < 1) ? 5 : this.pageSize;
// 获取数据
if (this.tagService.tagCloudList == null) {
this.tagService.getTagCloud();
}
if (this.countService.count == null) {
this.countService.getCount();
}
this.articleService.getArticle(this.pageNum, this.pageSize);
if (this.webUpdateService.updateInfoList == null) {
this.webUpdateService.getUpdateInfo();
}
if (!this.webUpdateService.lastestUpdateTime) {
this.webUpdateService.getLastestUpdateTime();
}
// 设置imagePath的初始值
this.reset();
}
showQQImg() {
this.imagePath = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/qq.jpg';
}
showWxImg() {
this.imagePath = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/wx.jpg';
}
reset() {
this.imagePath = 'https://56462271.oss-cn-beijing.aliyuncs.com/web/logo.png';
}
getPageArticle(e: number) {
this.pageNum = e;
// 修改地址栏
this.location.replaceState('', '?page=' + e + '&count=' + this.pageSize);
this.articleService.getArticle(this.pageNum, this.pageSize);
window.scrollTo(0, 0);
}
}

View File

@@ -0,0 +1,53 @@
ul {
list-style: none;
}
.leaveMsg-input {
width: 60%;
margin-left: 20%;
text-align: center;
}
.leaveMsg-input textarea {
padding: 10px;
max-height: 400px;
min-height: 60px;
width: 500px;
height: 100px;
border: 1px solid #4398ED;
border-radius: 10px;
margin-right: 20px;
}
#leaveMsg {
width: 70%;
height: 80%;
margin-top: 45px;
margin-left: 15%;
}
.submit-btn {
padding: 5px 10px;
}
.comment {
border: 1px solid #cccccc;
border-radius: 5px;
padding: 5px 10px;
}
#leaveMsgs {
width: 60%;
margin: 15px auto
}
@media only screen and ( max-width:768px) {
#leaveMsgs {
width: 96%;
margin-left: 2%;
}
#input-area {
width: 80%;
margin-left: 10%;
}
}

View File

@@ -0,0 +1,82 @@
<div>
<span *ngIf="!userService.userInfo" style="display: block;text-align: center">
若要评论,请先<a style="color: blue !important;" (click)="userService.showModal('login')">登录</a>哟~~~~
</span>
<nz-comment *ngIf="userService.userInfo" style="width: 60%;margin:15px auto">
<nz-comment-content>
<nz-form-item>
<textarea [(ngModel)]="content" nz-input style="height: 130px;width: 80%;"></textarea>
</nz-form-item>
<nz-form-item>
<button nz-button nzType="primary" [disabled]="!content" (click)="submitComment()">
评论
</button>
</nz-form-item>
</nz-comment-content>
</nz-comment>
<!-- 展示 -->
<nz-card id="leaveMsgs" [nzLoading]="!leaveMsgService.leaveMsgPage">
<nz-comment *ngFor="let leaveMsg of leaveMsgService.leaveMsgPage.list; let i = index"
[nzAuthor]="leaveMsg.authorName"
[nzDatetime]="leaveMsg.date">
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="leaveMsg.authorAvatarImgUrl"></nz-avatar>
<nz-comment-content class="comment">
<p>
{{leaveMsg.content}}
</p>
</nz-comment-content>
<nz-comment-action><span (click)="replyTo(leaveMsg.id,leaveMsg.authorName,i)">
<i nz-icon nzType="message" nzTheme="fill"></i> 回复</span>
</nz-comment-action>
<!-- 二级评论 -->
<ng-container *ngIf="leaveMsg.child && leaveMsg.child.length">
<nz-comment *ngFor="let secLeaveMsg of leaveMsg.child" [nzAuthor]="secLeaveMsg.authorName"
[nzDatetime]="secLeaveMsg.date">
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="secLeaveMsg.authorAvatarImgUrl"></nz-avatar>
<nz-comment-content class="comment">
<p>
{{secLeaveMsg.content}}
</p>
</nz-comment-content>
<!-- <nz-comment-action><span (click)="replyTo(secLeaveMsg.id,secLeaveMsg.authorName,i)">
<i nz-icon nzType="message" nzTheme="fill"></i> 回复</span>
</nz-comment-action> -->
</nz-comment>
</ng-container>
<!-- 二级评论的回复框 -->
<nz-comment *ngIf="responseComment.pid!=null&&relyIndex==i">
<nz-comment>
<nz-comment-content>
<nz-form-item>
<textarea [(ngModel)]="responseComment.content" nz-input
style="height: 130px;width: 80%;padding: 10px;border-radius: 5px;"></textarea>
</nz-form-item>
<nz-form-item>
<button nz-button nzType="primary" [disabled]="!responseComment.content"
(click)="reply()"> 回复
</button>
<button nz-button nzType="default" style="margin-left: 15px;" (click)="responseComment.pid=null"> 取消
</button>
</nz-form-item>
</nz-comment-content>
</nz-comment>
</nz-comment>
</nz-comment>
<nz-pagination style="text-align: center" [nzPageIndex]="pageNum" [nzTotal]="leaveMsgService.leaveMsgPage.total"
[nzPageSize]="pageSize"
[nzHideOnSinglePage]="true" (nzPageIndexChange)="toPage($event)"></nz-pagination>
</nz-card>
</div>

View File

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

View File

@@ -0,0 +1,107 @@
import {Component, OnInit} from '@angular/core';
import {Title} from '@angular/platform-browser';
import {NzMessageService} from 'ng-zorro-antd';
import {CommentService} from '../../services/comment/comment.service';
import {UserService} from '../../services/user/user.service';
import {CommentReq} from '../../class/commentReq';
@Component({
selector: 'app-leave-msg',
templateUrl: './leave-msg.component.html',
styleUrls: ['./leave-msg.component.css']
})
export class LeaveMsgComponent implements OnInit {
constructor(public leaveMsgService: CommentService,
private message: NzMessageService,
public userService: UserService,
private titleService: Title) {
titleService.setTitle('小海博客|留言');
// todo ::: @ {pid} 的name
}
pageNum: number = 1;
pageSize: number = 10;
content: string = null;
// 当前的回复框的所处位置
relyIndex: number;
public responseComment: CommentReq = new CommentReq(false);
ngOnInit() {
this.getPageLeaveMsg();
window.scrollTo(0, 0);
}
getPageLeaveMsg() {
this.leaveMsgService.getLeaveMsg(this.pageNum, this.pageSize);
}
/**
* 点击回复 显示输入框 及准备数据
* @param id 父级评论的id
* @param name 父级评论者的name
* @param index 索引
*/
replyTo(id, name, index) {
if (this.userService.userInfo == null) {
this.message.info('请先登录哟~~~');
return;
}
this.responseComment.pid = id;
this.responseComment.content = ' @' + name + ' ';
this.relyIndex = index;
}
toPage(a: number) {
if (a === this.pageNum) {
return;
}
this.pageNum = a;
this.getPageLeaveMsg();
}
/**
* 留言
*/
submitComment() {
if (this.content == null || this.content === '') {
this.message.info('内容不能为空');
return;
}
const submitBody: CommentReq = new CommentReq(false);
submitBody.content = this.content;
this.leaveMsgService.submitComment(submitBody);
}
/**
* 回复的数据提交
*/
reply() {
if (this.responseComment.content == null || this.responseComment.content === '') {
this.message.info('内容不能为空');
return;
}
if (this.responseComment.pid == null) {
this.message.error('非法操作');
}
this.responseComment.articleID = -1;
this.leaveMsgService.rely(this.responseComment).subscribe(data => {
if (data.code === 0) {
if (this.leaveMsgService.leaveMsgPage.list[this.relyIndex].child == null) {
this.leaveMsgService.leaveMsgPage.list[this.relyIndex].child = [];
}
this.leaveMsgService.leaveMsgPage.list[this.relyIndex].child.unshift(data.result);
this.responseComment.content = null;
this.responseComment.pid = null;
}
});
}
}

View File

@@ -0,0 +1,51 @@
.main {
}
input {
margin-bottom: 20px;
padding-left: 6px;
}
h2 {
font-weight: bold;
text-align: center
}
.main-content {
width: 500px;
position: relative;
left: 50%;
top: 50%;
transform: translateX(-50%)
}
#email, #password {
width: 100%;
border: 1px solid #999999;
border-radius: 5px;
height: 32px;
background: #ffffff
}
.br-text, .forget {
clear: both;
float: right;
}
.btn {
border: none;
padding: 4px 6px;
border-radius: 5px;
}
.login {
background: #0E90D2;
color: white
}
@media only screen and (max-width: 768px) {
.main-content {
width: 100%;
padding: 0 10px;
}
}

View File

@@ -0,0 +1,34 @@
<div class="main">
<div class="main-content">
<form>
<h1 style="text-align: center"><strong>小海</strong></h1>
<label for="email">邮箱:</label>
<br>
<input type="email" name="email" id="email" value="" [(ngModel)]="submitBody.email">
<br>
<label for="password">密码:</label>
<br>
<input type="password" name="password" id="password" value="" [(ngModel)]="submitBody.password">
<br>
<label for="remember-me">
<input id="remember-me" type="checkbox" name="isRemember" [(ngModel)]="submitBody.isRememberMe">
记住密码
</label>
<div class="br-text">
<p>
<span>还没有账号?</span>
<a style="color: blue" (click)="userService.showModal('registration')">注册</a>
</p>
</div>
<br/>
<div>
<button type="submit" (click)="doLogin()" class="btn login">登 录</button>
<button type="button" class="btn forget" (click)="showForgetPwd=!showForgetPwd">忘记密码 ^_^?</button>
</div>
</form>
</div>
</div>
<nz-modal [(nzVisible)]="showForgetPwd" nzTitle="重置密码" (nzOnCancel)="handleCancel()" (nzOnOk)="handleOk()">
<input type="email" nz-input placeholder="注册的邮箱" [(ngModel)]="email4Forgot"/>
</nz-modal>

View File

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

View File

@@ -0,0 +1,94 @@
import {Component, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Router} from '@angular/router';
import {UserService} from '../../services/user/user.service';
import {NzMessageService} from 'ng-zorro-antd';
import {LoginReq} from '../../class/loginReq';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor(public userService: UserService,
private routerinfo: ActivatedRoute,
private router: Router,
private message: NzMessageService) {
}
public submitBody: LoginReq = new LoginReq();
showForgetPwd: boolean = false;
email4Forgot: string;
ngOnInit() {
window.scrollTo(0, 0);
if (this.userService.tempUser) {
this.submitBody.email = this.userService.tempUser.email;
this.submitBody.password = this.userService.tempUser.password;
}
}
// 登录
doLogin() {
if (this.submitBody.email == null || this.submitBody.email === '') {
this.message.warning('邮箱不能为空');
return;
}
if (this.submitBody.password == null || this.submitBody.password === '') {
this.message.warning('密码不能为空');
return;
}
const regExp = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
if (!regExp.test(this.submitBody.email)) {
this.message.error('邮箱格式不合法');
return;
}
this.userService.login(this.submitBody).subscribe(data => {
if (data.code === 0) {
// 登录成功
// 置空
this.userService.tempUser = null;
this.message.success('登录成功,欢迎你' + data.result.displayName);
} else {
// 登录失败
this.message.error('登录失败,原因:' + data.msg);
}
});
}
handleCancel() {
this.showForgetPwd = false;
}
/**
* 发生重置密码的邮件
*/
handleOk() {
if (this.email4Forgot == null) {
return;
}
const regExp = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
if (!regExp.test(this.email4Forgot)) {
this.message.error('邮箱格式不合法');
return;
}
this.showForgetPwd = false;
this.userService.sendResetPwdEmail(this.email4Forgot).subscribe(data => {
if (data.code === 0) {
this.message.success('发送成功,请前往邮箱进行操作');
} else {
this.message.error(data.msg);
}
});
}
}

View File

@@ -0,0 +1,87 @@
div,
h1,
h2,
h3,
h4,
h5,
h6,
p,
img,
strong,
b,
i,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
footer,
header,
hgroup,
menu,
button,
input,
textarea {
margin: 0;
padding: 0;
border: 0;
outline: 0;
vertical-align: baseline;
background: transparent;
font-family: 'Microsoft Yahei', '\65B0\5B8B\4F53', '\5B8B\4F53', Verdana
}
.error404 {
width: 1100px;
height: 460px;
background: url(../../../assets/img/404.jpg) 50px;
background-size: 100% 100%;
margin: 3px auto 0
}
a {
color: #00c0ff !important;
}
.wenzi {
padding-top: 326px;
text-align: center;
font-family: "微软雅黑";
font-size: 26px;
color: #686e6e
}
.wenzi {
height: 56px;
line-height: 56px;
color: #888888;
display: block;
border-bottom: 1px solid #d0cfcf
}
.wenzi_2 span {
color: #319898;
font-size: 30px;
font-weight: bold;
font-style: italic
}
@media only screen and (max-width:768px) {
.error404 {
width: 100%;
background: url(../../../assets/img/404-m.jpg) -10px;
}
}

View File

@@ -0,0 +1,6 @@
<div class="error404">
<div class="wenzi">
<div class="wenzi-1">亲,您已经飘到外太空了哦!</div>
<div class="wenzi_2">想回地球请点击:<a href="/">小海博客</a></div>
</div>
</div>

View File

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

View File

@@ -0,0 +1,28 @@
import {Component, OnInit} from '@angular/core';
import {Router, NavigationError} from '@angular/router';
import {Location} from '@angular/common';
import {filter} from 'rxjs/operators';
import {Title} from '@angular/platform-browser';
@Component({
selector: 'app-not-found',
templateUrl: './not-found.component.html',
styleUrls: ['./not-found.component.css']
})
export class NotFoundComponent implements OnInit {
constructor(router: Router, location: Location, private titleService: Title) {
titleService.setTitle('小海博客|404');
router.events.pipe(
filter(event => event instanceof NavigationError)
).subscribe((event: NavigationError) => {
router.navigate(['/404'], {skipLocationChange: true})
.then(() => location.go(event.url));
});
}
ngOnInit() {
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,93 @@
.main {
width: 500px;
position: relative;
top: 50%;
left: 50%;
transform: translateX(-50%);
}
.ioc_text {
text-align: center
}
.remain {
}
.input-group {
margin: 20px 0 0 12%;
height: 32px;
}
input {
width: 75%;
height: 32px;
background: #fafafa;
border: 1px solid #ddd;
border-radius: 4px;
padding-left: 5px;
}
.verStatus {
width: 10px;
line-height: 32px;
margin-left: -20px;
margin-right: 20px;
}
img {
margin-left: 10px;
}
.br-text {
margin-left: 12%;
margin-top: 20px;
}
.btn {
width: 80%;
height: 40px;
margin-left: 10%;
border: none;
border-radius: 5px;
background: turquoise;
color: #ffffff;
}
a {
color: #3385FF
}
/* 全屏遮罩 */
.fullS {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
width: 100px;
height: 50px;
background: none;
transform: translate(-50%, -50%);
text-align: center
}
.loading span {
display: block;
}
@media only screen and (max-width: 768px) {
.main {
width: 100%;
}
}

View File

@@ -0,0 +1,56 @@
<div>
<div class="main">
<div class="ioc_text">
<h1><strong>小海</strong></h1>
</div>
<form>
<div class="remain">
<div class="input-group mb-4 bootint" id="username">
<!-- <div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-user"></i></span>
</div> -->
<input type="text" name="email" class="form-control" placeholder="请输入你的邮箱" [(ngModel)]="email">
</div>
<div class="input-group mb-4 bootint" id="password">
<!-- <div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-unlock-alt"></i></span>
</div> -->
<input type="password" name="password" class="form-control" placeholder="请输入你要设置的密码8位以上"
[(ngModel)]="password">
</div>
<div class="input-group mb-4 bootint" id="repassword">
<input type="password" name="repaassword" class="form-control" placeholder="请再次输入密码" [(ngModel)]="rePassword">
<i nz-icon class="verStatus" [hidden]="rePassword==null" [nzType]="password==rePassword?'check-circle':'close-circle'" nzTheme="outline"></i>
</div>
<div class="input-group">
<input type="text" name="code" placeholder="验证码" (blur)="handleKeyUp()" [(ngModel)]="imgCode"
class="form-control" style="width: 60%;" id="verifyImgCode">
<i nz-icon class="verStatus" [hidden]="imgCode==''" [nzType]="imgCodeStatus?'check-circle':'close-circle'" nzTheme="outline"></i>
<img [src]="imgCodeUrl" (click)="changeImg()" alt="Code" title="点击更换验证码"/>
</div>
<div class="br-text">
<p>
<span>已有账号了?</span>
<a (click)="userService.showModal('login')">登录</a>
</p>
</div>
<div style="padding-top: 10px">
<button type="submit" class="btn" (click)="doRegistration()">注册</button>
</div>
</div>
</form>
</div>
</div>
<div class="fullS" *ngIf="show">
<nz-spin [nzSpinning]="show" class="loading">
<div class="loading">
<span>加载中..</span>
</div>
</nz-spin>
</div>

View File

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

View File

@@ -0,0 +1,92 @@
import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {NzMessageService} from 'ng-zorro-antd';
import {UserService} from '../../services/user/user.service';
import {environment} from '../../../environments/environment';
import {LoginReq} from '../../class/loginReq';
@Component({
selector: 'app-registration',
templateUrl: './registration.component.html',
styleUrls: ['./registration.component.css']
})
export class RegistrationComponent implements OnInit {
imgCodeUrl: string = environment.host + '/imgCode';
// 遮罩
show = false;
// 输入框的验证码
imgCode: string = '';
email: string = null;
password: string = null;
rePassword: string = null;
// 验证码验证状态
public imgCodeStatus: boolean;
constructor(public userService: UserService,
private router: Router,
private message: NzMessageService) {
}
ngOnInit() {
window.scrollTo(0, 0);
}
changeImg() {
this.imgCodeUrl = environment.host + '/imgCode?time=' + (new Date()).getTime();
}
/**
* 验证码验证
*/
handleKeyUp() {
if (this.imgCode.length === 4) {
this.userService.imgCodeVerify(this.imgCode).subscribe(data => {
if (data.code === 0) {
this.imgCodeStatus = true;
} else {
this.imgCodeStatus = false;
this.message.warning('验证码验证失败');
}
});
}
}
/**
* 注册的数据提交
*/
doRegistration() {
if (this.imgCodeStatus) {
if (this.email == null || this.password == null || this.rePassword == null) {
this.message.warning('用户名和密码均不能为空');
return;
}
if (this.rePassword !== this.password) {
this.message.warning('两次密码不匹配');
return;
}
this.show = true;
this.userService.registration(this.email, this.password).subscribe(data => {
this.show = false;
this.userService.tempUser = new LoginReq();
if (data.code === 0) {
this.userService.tempUser.email = this.email;
this.userService.tempUser.password = this.password;
// 注册成功
this.message.success('注册成功!');
setTimeout(() => {
// 换成登录的modal
this.userService.loginModalType = 'login';
}, 300);
} else {
this.message.error('注册失败,原因:' + data.msg);
}
});
} else {
document.getElementById('verifyImgCode').focus();
}
}
}

View File

@@ -0,0 +1,27 @@
#main{
background: #ffffff;
border-radius: 5px;
width: 410px;
position: relative;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
padding: 5px;
}
button{
display: block;
border: none;
border-radius: 5px;
background: #9FFE8E;
height: 40px;
width: 400px;
}
input{
border: none;
text-align: center;
width: 400px;
border-radius: 5px;
height: 40px;
margin-bottom: 10px;
background: #eeeeee
}

View File

@@ -0,0 +1,13 @@
<div style="height: 70%">
<div id="main">
<div *ngIf="iserror">
<nz-alert nzType="error" nzMessage="链接可能被修改了,请重新点击邮箱中的链接,或者重新发送邮件" nzShowIcon>
</nz-alert>
</div>
<div *ngIf="!iserror">
<input type="password" placeholder="新密码" [(ngModel)]="pwd" />
<input type="password" placeholder="确认密码" [(ngModel)]="rePwd" />
<button (click)="submit()">提交</button>
</div>
</div>

View File

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

View File

@@ -0,0 +1,71 @@
import {Component, OnInit} from '@angular/core';
import {NzMessageService} from 'ng-zorro-antd';
import {Router, ActivatedRoute} from '@angular/router';
import {UserService} from '../../services/user/user.service';
@Component({
selector: 'app-reset-pwd',
templateUrl: './reset-pwd.component.html',
styleUrls: ['./reset-pwd.component.css']
})
export class ResetPwdComponent implements OnInit {
constructor(private message: NzMessageService,
public userService: UserService,
private router: Router,
private routerinfo: ActivatedRoute) {
}
private pwd: string;
private rePwd: string;
private email: string;
private verifyId: string;
iserror: boolean = false;
ngOnInit() {
this.email = this.routerinfo.snapshot.queryParams.email;
this.verifyId = this.routerinfo.snapshot.queryParams.verifyId;
if (this.email == null || this.verifyId == null) {
this.iserror = true;
}
}
submit() {
if (this.pwd == null) {
return;
}
if (this.pwd.length > 16) {
this.message.error('密码过长');
return;
}
if (this.pwd.length < 6) {
this.message.error('密码过短');
return;
}
if (this.pwd !== this.rePwd) {
this.message.warning('两次密码不一致');
}
const reqBody = {
email: this.email,
verifyId: this.verifyId,
pwd: this.pwd
};
this.userService.resetPWd(reqBody).subscribe(data => {
if (data.code === 0) {
this.message.success('重置密码成功,5秒后转跳到登录页面。');
setTimeout(() => {
this.router.navigateByUrl('/login');
// window.location.href = '/login';
}, 5000);
} else {
this.message.error(data.msg);
}
});
}
}

View File

@@ -0,0 +1,65 @@
#main{
width:60%;
margin:0 auto;
border-radius: 10px;
padding: 10px;
background: #ffffff;
}
#tag{
float: left;
width: 100%;
padding: 10px;
border: 1px solid #ececec;
box-shadow: 5px 5px 5px #888888;
margin-bottom: 20px;
}
#detail{
clear: both;
margin-top: 40px;
}
.singleTag{
background: #ffffff;
border-radius: 5px;
line-height: 15px;
margin: 5px 10px;
float: left;
cursor: pointer;
border: 1px solid #000000;
padding: 5px 8px;
}
ul{
margin: 0;
list-style: none;
}
.art{
padding: 8px;
border: 1px solid #ececec;
box-shadow: 5px 5px 5px #aaaaaa;
margin: 20px 0 ;
}
.title:hover{
animation: 1s move ;
}
@keyframes move{
from{
margin-left: 0;
}
to{
margin-left: 20px;
}
}
.active{
background: #6DAB90;
}
@media only screen and (max-width:768px){
#main{
width: 100%;
}
}

View File

@@ -0,0 +1,25 @@
<div id="main">
<div id="tag">
<ul>
<li *ngFor="let tag of tagService.tagCloudList" (click)="changeTag(tag.name)">
<span class="singleTag" [ngClass]="{active: currentTagName==tag.name}" [nzTitle]="'此标签有'+tag.size+'篇文章'"
nzPlacement="topCenter" nz-tooltip>
<i nz-icon nzType="tag" nzTheme="fill"></i> {{tag.name}}</span>
</li>
</ul>
</div>
<ul id="detail" *ngIf="curremtArticlePage">
<li *ngFor="let article of curremtArticlePage.list">
<div class="art">
<h2><a [routerLink]="'/article/'+article.id" class="title">{{article.title}}</a></h2>
<hr>
<div> {{article.summary}}</div>
</div>
</li>
</ul>
<div *ngIf="!curremtArticlePage">
<h2 style="text-align: center">该分类暂无文章</h2>
</div>
</div>

View File

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

View File

@@ -0,0 +1,62 @@
import {Component, OnInit} from '@angular/core';
import {Title} from '@angular/platform-browser';
import {Router, ActivatedRoute} from '@angular/router';
import {Location} from '@angular/common';
import {TagService} from '../../services/tag/tag.service';
import {ArticleService} from '../../services/article/article.service';
import {Page} from '../../class/page';
import {Article} from '../../class/article';
@Component({
selector: 'app-tags',
templateUrl: './tag.component.html',
styleUrls: ['./tag.component.css']
})
export class TagComponent implements OnInit {
constructor(private titleService: Title,
public tagService: TagService,
public articleService: ArticleService,
private router: Router,
private routerinfo: ActivatedRoute,
private location: Location) {
titleService.setTitle('小海博客|标签');
}
public currentTagName: string;
public curremtArticlePage: Page<Article>;
ngOnInit() {
window.scrollTo(0, 0);
this.currentTagName = this.routerinfo.snapshot.queryParams.name;
if (this.tagService.tagCloudList == null) {
this.tagService.getTagCloud().subscribe((data: any) => {
if (data.code === 0) {
// 根据当前获取到的tag name获取数据
if (this.currentTagName == null) {
this.currentTagName = data.result[0].name;
}
this.getArticle();
}
});
} else if (this.currentTagName == null) {
this.currentTagName = this.tagService.tagCloudList[0].name;
}
this.getArticle();
}
changeTag(name) {
this.currentTagName = name;
this.location.replaceState('tag', '?name=' + name);
this.getArticle();
}
getArticle() {
this.articleService.getArticleByTag(this.currentTagName).subscribe(data => {
this.curremtArticlePage = data.result;
});
}
}

View File

@@ -0,0 +1,96 @@
@media screen and (min-width: 768px) {
.site-inner{
margin: 0 19%;
}
.word p{
text-indent: 7em;
}
.word{
padding: 20px 25px;
}
.zh-update{
margin: 0 25%;
}
.update-leave-message{
margin: 100px 10%;
}
}
@media screen and (max-width: 768px) {
.site-inner{
margin: 0 5%;
}
.word p{
text-indent: 2em;
}
.word{
padding: 20px 5px;
}
.zh-update{
margin: 0 3%;
}
}
.am-g{
margin: 50px 0 0;
}
.site-inner-img{
text-align: center;
margin-top: 50px;
}
.site-inner-img img{
width: 450px;
margin: 0 auto;
max-width: 100%;
}
.site-inner-mywords{
margin: 30px auto;
}
.word{
margin-top: 10px;
margin-left: 25%;
font-size: 21px;
line-height: 20px;
color: blue;
font-family: 仿;
}
.zh-update-log-title{
margin: 50px 0;
}
.zh-update-log-title h1{
font-size: 22px;
font-weight: bold;
color: #222;
text-indent: 1em;
}
.zh-update-log-title p{
text-indent: 1.5em;
}
.zh-update-log-content{
margin-top: 10px;
}
.update-log i{
color: #999999;
margin-right: 5px;
}
.update-log h2{
display: inline-block;
font-size: 1.3em;
font-weight: 650;
color: #333;
}
.update-log-content{
margin: 15px 26px;
}
.update-log-content blockquote{
padding: 0 10px 0 20px;
border-left: 4px solid #ddd;
}
.update-log-content p{
font-size: 16px;
word-wrap: break-word;
font-family: ;
}
.container{
padding-left: 1.5rem;
padding-right: 1.5rem;
}

View File

@@ -0,0 +1,56 @@
<div id="app">
<!--页面主体-->
<div id="main">
<div class="container">
<div class="site-inner">
<div class="site-inner-img">
<img src="https://zhy-myblog.oss-cn-shenzhen.aliyuncs.com/static/img/sun.jpg" class="am-img-thumbnail">
</div>
<div class="site-inner-mywords">
<br>
<div class="word">
过自己喜欢的生活,<br><br>
<p>做自己喜欢的人</p><br>
<p>不以物喜</p>
<p>不以己悲</p>
</div>
<br>
</div>
</div>
<div class="zh-update">
<div class="zh-update-log">
<div class="zh-update-log-title">
<h1>更新日志</h1>
<p>最后更新于 {{updateService.lastestUpdateTime}}</p>
</div>
<div class="zh-update-log-content">
<div class="update-log am-animation-slide-bottom" *ngFor="let update of updateService.updateInfoList">
<div class="update-log-date">
<i nz-icon nzType="star" nzTheme="twotone" nzTwotoneColor="#eb2f96" class="titleTag"></i>
<h2>{{update.time}}</h2>
</div>
<div class="update-log-content">
<blockquote>
<p *ngFor="let item of update.info.split('\n')">{{item}}</p>
</blockquote>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

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

View File

@@ -0,0 +1,28 @@
import {Component, OnInit} from '@angular/core';
import {Title} from '@angular/platform-browser';
import {WebUpdateService} from '../../services/update/web-update.service';
@Component({
selector: 'app-update',
templateUrl: './update.component.html',
styleUrls: ['./update.component.css']
})
export class UpdateComponent implements OnInit {
constructor(public updateService: WebUpdateService,
private titleService: Title) {
titleService.setTitle('小海博客|更新');
}
ngOnInit() {
if (this.updateService.updateInfoList == null) {
this.updateService.getUpdateInfo();
}
window.scrollTo(0, 0);
if (!this.updateService.lastestUpdateTime) {
this.updateService.getLastestUpdateTime();
}
}
}

View File

@@ -0,0 +1,30 @@
import {AfterViewInit, Attribute, Directive, EventEmitter, Input, Output} from '@angular/core';
import {EditorConfig} from '../../../class/editor-config';
declare var editormd: any;
declare var $: any;
@Directive({
selector: '[appEditorMd]'
})
export class EditorMdDirective implements AfterViewInit {
@Input() editormdConfig: EditorConfig; // 配置选项
@Output() onEditorChange: EventEmitter<string> = new EventEmitter<string>(); // 发射器
editor: any; // editormd编辑器
constructor(@Attribute('id') private id: string) {
}
ngAfterViewInit(): void {
this.editor = editormd(this.id, this.editormdConfig); // 创建编辑器
const out = this.onEditorChange;
const textarea = $('#' + this.id + ' :first'); // 获取textarea元素
// 当编辑器内容改变时触发textarea的change事件
// tslint:disable-next-line:only-arrow-functions
this.editor.on('change', function() {
out.emit(textarea.val());
});
}
}

View File

@@ -0,0 +1,133 @@
/* @import '../../../../../assets/editormd/editor.css';
.fa {
display: none;
} */
h2 {
text-align: center
}
.con {
display: flex;
flex-wrap: wrap
}
#title {
/* 85% */
flex-grow: 1;
margin: 55px 0 5px 3%;
height: 35px;
border-radius: 5px;
border: none;
padding-left: 6px;
}
#submit {
margin: 55px 3% 5px 20px;
width: 100px;
height: 35px;
background: #D83731;
border: none;
border-radius: 5px;
color: white
}
#md {
min-width: 1200px;
width: 100%;
}
/*发布博客模态框设置*/
.am-modal-bd {
border-bottom: none;
}
.article-tag,
.article-type,
.articleUrlHide {
padding: 10px 20px 10px;
}
.row {
text-align: left;
}
.publish-tag {
display: inline-block;
font-weight: 400;
}
.tags,
.type,
.tag,
.tag-inline,
.categories,
.grade,
.Url,
.tabloid {
display: inline-block;
}
.singleTag {
background: #ececec;
border-radius: 3px;
margin: 0 2px;
padding: 1px;
}
.taginput {
width: 300px;
height: 32px;
background: #fafafa;
border: 1px solid #ddd;
border-radius: 4px;
}
.removeTag {
margin-top: 10px;
margin-left: 1px;
font-size: 16px;
height: 12px;
color: #ddd;
transition: color .3s ease-in;
vertical-align: -1px;
cursor: pointer;
}
.addTagsBtn {
margin-left: 5px;
display: inline-block;
padding: 0;
background-color: transparent;
border: none;
color: #349edf;
font-size: 14px;
line-height: 32px;
}
#select-type,
#select-categories,
#select-grade {
width: 138px;
height: 32px;
background: #fafafa;
border: 1px solid #ddd;
border-radius: 4px;
}
.required {
margin-left: 2px;
font-size: 12px;
color: #ca0c16;
}
#articleUrl {
width: 300px;
height: 32px;
background: #fafafa;
border: 1px solid #ddd;
border-radius: 4px;
}

View File

@@ -0,0 +1,62 @@
<div style="margin-top: -80px;margin-bottom: -130px;z-index: 1000">
<div class="con">
<input type="text" [(ngModel)]="article.title" id="title">
<button id="submit" (click)="articleSubmit()">提交</button>
</div>
<div id="md" appEditorMd [editormdConfig]="conf" (onEditorChange)="syncModel($event)">
<textarea style="display: block;" [(ngModel)]="article.mdContent"></textarea>
</div>
</div>
<nz-modal [(nzVisible)]="showPupup" nzTitle="发布博文" (nzOnCancel)="showPupup=false" (nzOnOk)="publishArticle()">
<div class="am-modal-bd">
<div class="article-setting">
<div class="article-type row">
<label class="publish-tag">文章类型<strong>:</strong></label>
<div class="type">
<select id="select-type" [(ngModel)]="article.type">
<option>请选择</option>
<option [ngValue]="true">原创</option>
<option [ngValue]="false">转载</option>
</select>
<span class="required">*</span>
</div>
</div>
<div class="article-type row">
<label class="publish-tag">博客分类<strong>:</strong></label>
<div class="categories">
<select id="select-categories" [(ngModel)]="article.category">
<option class="categoriesOption" value="">请选择</option>
<option class="categoriesOption" *ngFor="let category of categoryService.categories"
value={{category.name}}>{{category.name}}</option>
</select>
<span class="required">*</span>
</div>
</div>
<div class="article-tag row">
<div class="tags">
<div class="tag-inline">
<label class="publish-tag">文章标签<strong>:</strong></label>
<div class="tag">
</div>
<input class="taginput" placeholder="每个标签以英文','结束" [(ngModel)]="article.tags"><span
class="required">*</span>
</div>
<span style="display: block;margin-left:67px;" *ngIf="article.tags">
<span *ngFor="let tag of article.tags.split(',')" class="singleTag">{{tag}}</span>
</span>
</div>
</div>
<div class="articleUrlHide row" *ngIf="!article.type">
<label class="publish-tag" style="display: inline-block">原文链接<strong>:</strong></label>
<div class="url" style="display: inline-block">
<input type="text" id="articleUrl" [(ngModel)]="article.url" placeholder=" 请输入原文链接">
<span class="required">*</span>
</div>
</div>
</div>
</div>
</nz-modal>

View File

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

View File

@@ -0,0 +1,183 @@
import {Component, OnInit, Output, Input, EventEmitter} from '@angular/core';
import {EditorConfig} from '../../class/editor-config';
import {ActivatedRoute} from '@angular/router';
import {Router} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {NzMessageService} from 'ng-zorro-antd';
import {ArticleService} from '../../services/article/article.service';
import {CategoryService} from '../../services/category/category.service';
import {ArticleReq} from '../../class/articleReq';
import {UserService} from '../../services/user/user.service';
@Component({
selector: 'app-write',
templateUrl: './write.component.html',
styleUrls: ['./write.component.css']
})
export class WriteComponent implements OnInit {
constructor(public articleService: ArticleService,
private routerinfo: ActivatedRoute,
private router: Router,
private titleService: Title,
private message: NzMessageService,
public categoryService: CategoryService,
public userService: UserService) {
titleService.setTitle('小海博客|创作');
}
public obj: any;
show = false;
msg: any;
conf = new EditorConfig();
articleId;
isUpdate = false;
public article: ArticleReq = new ArticleReq();
showPupup = false;
// 同步属性内容
syncModel(str): void {
this.article.mdContent = str;
}
ngOnInit() {
this.articleId = this.routerinfo.snapshot.queryParams.id;
if (this.articleId != null) {
this.isUpdate = true;
this.getArticle();
}
if (!this.articleId && localStorage.getItem('tmpArticle')) {
this.article = JSON.parse(localStorage.getItem('tmpArticle'));
}
this.setSuitableHeight();
// 用户权限判断
if (!this.userService.userInfo) {
this.userService.getUserInfo().subscribe(data => {
if (!data.result || data.result.role !== 'admin') {
this.message.info('你暂时无发布文章的权限,所写文章将暂存在本地');
}
});
} else {
if (this.userService.userInfo.role !== 'admin') {
this.message.info('你暂时无发布文章的权限,所写文章将暂存在本地');
}
}
}
/**
* 设置高度
*/
setSuitableHeight() {
this.conf.height = (window.innerHeight - 80 - 50) + '';
}
// 提交按钮的事件
articleSubmit() {
if (this.article.title === '' || this.article.mdContent === '') {
this.message.warning(this.article.title === '' ? '标题不能为空' : '文章不能为空');
return;
}
this.showPupup = true;
if (!this.categoryService.categories) {
this.categoryService.getAllCategory();
}
}
/**
* 文章数据提交
*/
publishArticle() {
// 去除空值
this.article.tags = this.article.tags.split(',').filter(item => item !== '').toString();
if (this.article.tags === '') {
this.message.warning('文章标签不能为空');
return;
}
if (this.article.type !== (true || false)) {
this.message.warning('文章类型不能为空');
return;
}
if (this.article.category === '') {
this.message.warning('文章分类不能为空');
return;
}
if (!this.article.type) {
const regExp = /^http(s|):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$/;
if (regExp.test(this.article.url)) {
this.message.warning('原文链接不合法');
return;
}
}
// 文章 暂存
localStorage.setItem('tmpArticle', JSON.stringify(this.article));
this.article.url = this.article.type ? null : this.article.url;
if (!this.isUpdate) {
// 非文章更新
this.articleService.createArticle(this.article).subscribe(data => {
if (data.code === 0) {
// TODO 成功
this.message.success('发布成功,即将转跳');
localStorage.removeItem('tmpArticle');
setTimeout(() => {
this.router.navigateByUrl('article/' + data.result.id);
}, 2500);
}
if (data.code === 301) {
// 未登陆
this.router.navigateByUrl('login');
}
if (data.code === 302) {
this.message.error('你没有发布文章的权限');
}
});
} else {
// 文章更新
this.articleService.updateArticle(this.article).subscribe(data => {
// TODO 未登陆
if (data.code === 0) {
this.message.success('更新成功,即将转跳');
localStorage.removeItem('tmpArticle');
setTimeout(() => {
this.router.navigateByUrl('article/' + data.result.id);
}, 2500);
} else if (data.code === 301) {
this.router.navigateByUrl('login');
} else if (data.code === 302) {
this.message.error('你没有更新文章的权限');
} else {
this.message.error('失败,原因:' + data.msg);
}
});
}
}
/**
* 获取文章 for update
*/
getArticle() {
this.articleService.getArticleById(this.articleId, true).subscribe(data => {
if (data.code === 0) {
this.article.category = data.result.category;
this.article.mdContent = data.result.mdContent;
this.article.tags = String(data.result.tags);
this.article.type = data.result.original;
this.article.url = data.result.url;
this.article.title = data.result.title;
this.article.id = data.result.id;
} else if (data.code === 201) {
// 文章不存在
this.message.error('文章不存在');
}
});
}
}