修改路径
This commit is contained in:
585
src/app/api/api.service.ts
Normal file
585
src/app/api/api.service.ts
Normal file
@@ -0,0 +1,585 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
|
||||
import {Article} from '../class/Article';
|
||||
import {HttpService} from './http/http.service';
|
||||
import {PageList} from '../class/HttpReqAndResp';
|
||||
import {ErrDispatch} from '../class/ErrDispatch';
|
||||
import {ArticleReq} from '../class/Article';
|
||||
import {Category, Tag} from '../class/Tag';
|
||||
import {Comment} from '../class/Comment';
|
||||
import {CommentReq} from '../class/Comment';
|
||||
import {Link} from '../class/Link';
|
||||
import {User} from '../class/User';
|
||||
import {LoginReq} from '../class/User';
|
||||
|
||||
import {LocalStorageService} from '../services/local-storage.service';
|
||||
import {Visitor} from '../class/Visitor';
|
||||
import {UpdateInfo} from '../class/UpdateInfo';
|
||||
|
||||
@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
|
||||
});
|
||||
}
|
||||
|
||||
deleteArticle(id: number) {
|
||||
return super.Service<boolean>({
|
||||
path: '/admin/article/del',
|
||||
method: 'DELETE',
|
||||
queryParam: {articleID: id}
|
||||
})
|
||||
}
|
||||
|
||||
articles(pageNumber: number = 1, pageSize: number = 5) {
|
||||
return super.Service<PageList<Article>>({
|
||||
path: '/articles',
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
page: pageNumber,
|
||||
count: pageSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
adminArticles(pageNumber: number = 1, pageSize: number = 10) {
|
||||
return super.Service<PageList<Article>>({
|
||||
path: '/admin/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'
|
||||
});
|
||||
}
|
||||
|
||||
createCategory(nameStr: string) {
|
||||
return super.Service<Category>({
|
||||
path: '/admin/category/create',
|
||||
method: 'POST',
|
||||
queryParam: {name: nameStr}
|
||||
});
|
||||
}
|
||||
|
||||
deleteCategory(categoryId: number) {
|
||||
return super.Service<boolean>({
|
||||
path: '/admin/category/del',
|
||||
method: 'DELETE',
|
||||
queryParam: {id: categoryId}
|
||||
});
|
||||
}
|
||||
|
||||
updateCategory(categoryId: number, nameStr: string) {
|
||||
return super.Service<Category>({
|
||||
path: '/admin/category/update',
|
||||
method: 'PUT',
|
||||
queryParam: {id: categoryId, name: nameStr}
|
||||
});
|
||||
}
|
||||
|
||||
tags(pageNumber: number = 1, pageSize: number = 10) {
|
||||
return super.Service<PageList<Tag>>({
|
||||
path: '/tags',
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
page: pageNumber,
|
||||
count: pageSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tagsNac() {
|
||||
return super.Service<{ name: string, size: number }[]>({
|
||||
path: '/tags/nac',
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
createTag(nameStr: string) {
|
||||
return super.Service<Tag>({
|
||||
path: '/admin/tag/create',
|
||||
method: 'POST',
|
||||
queryParam: {name: nameStr}
|
||||
});
|
||||
}
|
||||
|
||||
deleteTag(TagId: number) {
|
||||
return super.Service<boolean>({
|
||||
path: '/admin/tag/del',
|
||||
method: 'DELETE',
|
||||
queryParam: {id: TagId}
|
||||
});
|
||||
}
|
||||
|
||||
updateTag(TagId: number, nameStr: string) {
|
||||
return super.Service<Tag>({
|
||||
path: '/admin/tag/update',
|
||||
method: 'PUT',
|
||||
queryParam: {id: TagId, name: nameStr}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
getCommentByPid(pid: number, pageNumber: number = 1, pageSize: number = 10) {
|
||||
return super.Service<Comment[]>({
|
||||
path: `/comment/pid/${pid}`,
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
page: pageNumber,
|
||||
count: pageSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCommentByTypeForAdmin(isComment: boolean, pageNumber: number = 1, pageSize: number = 10) {
|
||||
return super.Service<PageList<Comment>>({
|
||||
path: `/admin/comment/type/${isComment ? 1 : 0}`,
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
page: pageNumber,
|
||||
count: pageSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCommentByTypeForUser(isComment: boolean, pageNumber: number = 1, pageSize: number = 10) {
|
||||
return super.Service<PageList<Comment>>({
|
||||
path: `/user/comment/type/${isComment ? 1 : 0}`,
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
page: pageNumber,
|
||||
count: pageSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteComment(idNumer: number) {
|
||||
return super.Service<boolean>({
|
||||
path: `/user/comment/del`,
|
||||
method: 'DELETE',
|
||||
queryParam: {id: idNumer}
|
||||
});
|
||||
}
|
||||
|
||||
updateComment(commentReq: CommentReq) {
|
||||
return super.Service<Comment>({
|
||||
path: `/user/comment/update`,
|
||||
method: 'PUT',
|
||||
data: commentReq,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
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'
|
||||
});
|
||||
}
|
||||
|
||||
adminLinks(pageSize: number = 10, pageNumber: number = 1) {
|
||||
return super.Service<PageList<Link>>({
|
||||
path: '/admin/links',
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
count: pageSize,
|
||||
page: pageNumber
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createLink(linkReq: Link) {
|
||||
return super.Service<Link>({
|
||||
path: '/admin/links/create',
|
||||
method: 'POST',
|
||||
data: linkReq,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
deleteLink(idNumber: number) {
|
||||
return super.Service<boolean>({
|
||||
path: `/admin/links/del/${idNumber}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
updateLink(linkReq: Link) {
|
||||
return super.Service<Link>({
|
||||
path: '/admin/links/update',
|
||||
method: 'PUT',
|
||||
data: linkReq,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
return super.Service<User>({
|
||||
path: '/login',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: loginReq
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
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() {
|
||||
return super.Service<User>({
|
||||
path: '/user/userInfo',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
adminUpdateUser(user: User) {
|
||||
return super.Service<User>({
|
||||
path: '/admin/user',
|
||||
method: 'PUT',
|
||||
data: user,
|
||||
contentType: 'application/json'
|
||||
})
|
||||
}
|
||||
|
||||
deleteUser(id: number) {
|
||||
return super.Service<boolean>({
|
||||
path: `/admin/user/delete/${id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
multipleDeleteUser(idArray: number[]) {
|
||||
return super.Service<{ id: number; msg: string; status: boolean }[]>({
|
||||
path: `/admin/user/delete`,
|
||||
method: 'DELETE',
|
||||
data: idArray,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取邮件是否已注册
|
||||
emailStatus(email: string) {
|
||||
return super.Service<boolean>({
|
||||
path: `/emailStatus/${email}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
updateUserInfo(descStr: string, disPlayNameStr: string) {
|
||||
return super.Service<User>({
|
||||
path: '/user/userInfo/update',
|
||||
method: 'PUT',
|
||||
queryParam: {
|
||||
desc: descStr,
|
||||
displayName: disPlayNameStr
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
adminUsers(pageSize: number = 10, pageNumber: number = 1) {
|
||||
return super.Service<PageList<User>>({
|
||||
path: '/admin/users',
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
count: pageSize,
|
||||
page: pageNumber
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
visit() {
|
||||
return super.Service<Visitor>({
|
||||
path: '/visit',
|
||||
method: 'POST'
|
||||
});
|
||||
}
|
||||
|
||||
adminVisitors(location: boolean = false, pageSize: number = 10, pageNumber: number = 1) {
|
||||
return super.Service<PageList<Visitor>>({
|
||||
path: '/admin/visitor/page',
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
count: pageSize,
|
||||
page: pageNumber,
|
||||
showLocation: location
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dayVisitCount() {
|
||||
return super.Service<number>({
|
||||
path: '/dayVisitCount',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
getLocalIp() {
|
||||
return super.Service<string>({
|
||||
path: '/ip',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
getIpLocation(ip: string) {
|
||||
return super.Service<string>({
|
||||
path: `/ip/${ip}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
visitorCount() {
|
||||
return super.Service<number>({
|
||||
path: `/visitor/count`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
webUpdate() {
|
||||
return super.Service<{ id: number, info: string, time: string }[]>({
|
||||
path: '/webUpdate',
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
webUpdatePage(pageSize: number = 10, pageNumber: number = 1) {
|
||||
return super.Service<PageList<{ id: number, info: string, time: string }>>({
|
||||
path: '/webUpdate/pages',
|
||||
method: 'GET',
|
||||
queryParam: {
|
||||
count: pageSize,
|
||||
page: pageNumber,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lastestUpdate() {
|
||||
return super.Service<{
|
||||
lastUpdateTime: string;
|
||||
lastUpdateInfo: string;
|
||||
lastCommit: string;
|
||||
committerAuthor: string;
|
||||
committerDate: string;
|
||||
commitUrl: string
|
||||
}>({
|
||||
path: '/lastestUpdate',
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
createWebUpdateInfo(infoStr: string) {
|
||||
return super.Service<UpdateInfo>({
|
||||
path: '/admin/webUpdate/create',
|
||||
method: 'POST',
|
||||
queryParam: {info: infoStr}
|
||||
});
|
||||
}
|
||||
|
||||
deleteWebUpdateInfo(idNumber: number) {
|
||||
return super.Service<boolean>({
|
||||
path: `/admin/webUpdate/del/${idNumber}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
updateWebUpdateInfo(idNumber: number, infoStr: string) {
|
||||
return super.Service<UpdateInfo>({
|
||||
path: '/admin/webUpdate/update',
|
||||
method: 'PUT',
|
||||
queryParam: {id: idNumber, info: infoStr}
|
||||
});
|
||||
}
|
||||
|
||||
bingPic() {
|
||||
return super.Service<string>({
|
||||
path: '/bingPic',
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
126
src/app/api/http/http.service.ts
Normal file
126
src/app/api/http/http.service.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {RequestObj} from '../../class/HttpReqAndResp';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {environment} from '../../../environments/environment';
|
||||
import {LocalStorageService} from '../../services/local-storage.service';
|
||||
import {Response} from '../../class/HttpReqAndResp';
|
||||
import {Observable, Observer, Subject} from 'rxjs';
|
||||
import {ErrDispatch} from '../../class/ErrDispatch';
|
||||
|
||||
@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' : request.contentType;
|
||||
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;
|
||||
}
|
||||
}
|
||||
41
src/app/app-routing.module.ts
Normal file
41
src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
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: '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: 'emailVerify',
|
||||
loadChildren: () => import('./view/email-verify/email-verify.module').then(mod => mod.EmailVerifyModule)
|
||||
},
|
||||
{
|
||||
path: 'user', loadChildren: () => import('./view/login-registration/login-registration.module')
|
||||
.then(mod => mod.LoginRegistrationModule)
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () => import('./view/admin/admin.module').then(mod => mod.AdminModule),
|
||||
},
|
||||
{
|
||||
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 {
|
||||
}
|
||||
33
src/app/app.component.html
Normal file
33
src/app/app.component.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<ng-container *ngIf="componentStateService.visible.header">
|
||||
<app-header (loginEvent)="login()" (registrationEvent)="registration()" #headerComponent></app-header>
|
||||
</ng-container>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
<ng-container *ngIf="componentStateService.visible.footer">
|
||||
<app-footer></app-footer>
|
||||
</ng-container>
|
||||
|
||||
<nz-back-top [nzTemplate]="backToTop" *ngIf="componentStateService.visible.globalBackToTop"></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>
|
||||
|
||||
<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>
|
||||
20
src/app/app.component.less
Normal file
20
src/app/app.component.less
Normal 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;
|
||||
}
|
||||
35
src/app/app.component.spec.ts
Normal file
35
src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {TestBed, async} from '@angular/core/testing';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title '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!');
|
||||
});
|
||||
});
|
||||
42
src/app/app.component.ts
Normal file
42
src/app/app.component.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import {Component, ElementRef, OnInit, TemplateRef, ViewChild} from '@angular/core';
|
||||
import {LoginReq} from './class/User';
|
||||
import {HeaderComponent} from './components/header/header.component';
|
||||
import {ComponentStateService} from './services/component-state.service';
|
||||
|
||||
@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;
|
||||
|
||||
constructor(public componentStateService: ComponentStateService) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
40
src/app/app.module.ts
Normal file
40
src/app/app.module.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
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';
|
||||
import {AdminModule} from './view/admin/admin.module';
|
||||
|
||||
|
||||
registerLocaleData(zh);
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeaderComponent,
|
||||
FooterComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
NgZorroAntdModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
BrowserAnimationsModule,
|
||||
LoginRegistrationModule,
|
||||
AdminModule
|
||||
],
|
||||
providers: [{provide: NZ_I18N, useValue: zh_CN}],
|
||||
exports: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
35
src/app/class/Article.ts
Normal file
35
src/app/class/Article.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
export class ArticleReq {
|
||||
category: string;
|
||||
id?: number;
|
||||
mdContent: string;
|
||||
open: boolean;
|
||||
tags: string;
|
||||
title: string;
|
||||
type: boolean;
|
||||
url?: string;
|
||||
|
||||
constructor() {
|
||||
this.type = true;
|
||||
}
|
||||
}
|
||||
33
src/app/class/Comment.ts
Normal file
33
src/app/class/Comment.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export class Comment {
|
||||
id?: number;
|
||||
authorName?: string;
|
||||
authorAvatarImgUrl?: string;
|
||||
content: string;
|
||||
articleID: number;
|
||||
articleTitle: string;
|
||||
date?: string;
|
||||
responseId: string;
|
||||
pid: number;
|
||||
comment: boolean;
|
||||
respComment: Comment[];
|
||||
}
|
||||
|
||||
|
||||
export class CommentReq {
|
||||
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;
|
||||
}
|
||||
this.pid = -1;
|
||||
this.id = null;
|
||||
}
|
||||
}
|
||||
27
src/app/class/EditorConfig.ts
Normal file
27
src/app/class/EditorConfig.ts
Normal 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 = false; // 时序图/序列图
|
||||
public imageUpload = true;
|
||||
public imageFormats = ['jpg', 'jpeg', 'gif', 'png', 'bmp', 'webp'];
|
||||
public imageUploadURL = environment.host + '/imgUpload';
|
||||
public useAjaxToUploadImg = true
|
||||
}
|
||||
5
src/app/class/ErrDispatch.ts
Normal file
5
src/app/class/ErrDispatch.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import {RequestObj} from './HttpReqAndResp';
|
||||
|
||||
export interface ErrDispatch {
|
||||
errHandler(code: number, msg: string, request?: RequestObj): void;
|
||||
}
|
||||
45
src/app/class/HttpReqAndResp.ts
Normal file
45
src/app/class/HttpReqAndResp.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
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[];
|
||||
};
|
||||
}
|
||||
|
||||
export class Response<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
result: T;
|
||||
date: number;
|
||||
|
||||
constructor(t: T) {
|
||||
this.code = 0;
|
||||
this.result = t;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
6
src/app/class/Link.ts
Normal file
6
src/app/class/Link.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class Link {
|
||||
id?: number;
|
||||
name: string;
|
||||
url: string;
|
||||
open?: boolean;
|
||||
}
|
||||
12
src/app/class/Tag.ts
Normal file
12
src/app/class/Tag.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export class Category {
|
||||
id: number;
|
||||
name: string;
|
||||
articles?: number[];
|
||||
}
|
||||
|
||||
|
||||
export class Tag {
|
||||
id: number;
|
||||
name: string;
|
||||
articles?: number[];
|
||||
}
|
||||
5
src/app/class/UpdateInfo.ts
Normal file
5
src/app/class/UpdateInfo.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class UpdateInfo {
|
||||
id: number;
|
||||
info: string;
|
||||
time: string;
|
||||
}
|
||||
24
src/app/class/User.ts
Normal file
24
src/app/class/User.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export class User {
|
||||
id: number;
|
||||
email: string;
|
||||
displayName: string;
|
||||
emailStatus: boolean;
|
||||
avatarImgUrl?: string;
|
||||
desc: string;
|
||||
role: string;
|
||||
token?: string;
|
||||
pwd?: string;
|
||||
recentlyLandedDate?: string
|
||||
}
|
||||
|
||||
export class LoginReq {
|
||||
email: string;
|
||||
isRememberMe: boolean;
|
||||
password: string;
|
||||
|
||||
constructor(email: string, isRememberMe: boolean, password: string) {
|
||||
this.email = email;
|
||||
this.isRememberMe = isRememberMe;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
9
src/app/class/Visitor.ts
Normal file
9
src/app/class/Visitor.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class Visitor {
|
||||
id: number;
|
||||
ip: string;
|
||||
date: string;
|
||||
browserName: string;
|
||||
browserVersion: string;
|
||||
osname: string;
|
||||
location: string;
|
||||
}
|
||||
34
src/app/components/admin-header/admin-header.component.html
Normal file
34
src/app/components/admin-header/admin-header.component.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<header>
|
||||
<a href="/">
|
||||
<div>
|
||||
<img src="https://56462271.oss-cn-beijing.aliyuncs.com/web/logo.png"/>
|
||||
</div>
|
||||
</a>
|
||||
<div>
|
||||
<a routerLink="/" id="blogTitle"><span>小海博客</span></a>
|
||||
<span id="desc">记录学习成长历程</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="loged" *ngIf="user" (click)="infoClickedEvent()">
|
||||
<nz-avatar [nzSrc]="user.avatarImgUrl"></nz-avatar>
|
||||
<span>{{user.displayName}}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="landr" *ngIf="!user">
|
||||
<nz-space>
|
||||
<nz-space-item>
|
||||
<a routerLink="/user/login">
|
||||
<button nz-button nzType="primary">登录</button>
|
||||
</a>
|
||||
</nz-space-item>
|
||||
<nz-space-item>
|
||||
<a routerLink="/user/registration">
|
||||
<button nz-button nzType="primary">注册</button>
|
||||
</a>
|
||||
</nz-space-item>
|
||||
</nz-space>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
47
src/app/components/admin-header/admin-header.component.less
Normal file
47
src/app/components/admin-header/admin-header.component.less
Normal file
@@ -0,0 +1,47 @@
|
||||
header {
|
||||
height: 70px;
|
||||
background-color: #ffffff;
|
||||
opacity: 0.8;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
min-width: 350px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin: 5px 5px 5px 30px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#landr, #loged {
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
top: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
#blogTitle, #desc {
|
||||
display: block;
|
||||
margin-left: 15px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
#blogTitle {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#desc {
|
||||
font-size: 10px;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AdminHeaderComponent } from './admin-header.component';
|
||||
|
||||
describe('AdminHeaderComponent', () => {
|
||||
let component: AdminHeaderComponent;
|
||||
let fixture: ComponentFixture<AdminHeaderComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AdminHeaderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AdminHeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
29
src/app/components/admin-header/admin-header.component.ts
Normal file
29
src/app/components/admin-header/admin-header.component.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
|
||||
import {GlobalUserService} from '../../services/global-user.service';
|
||||
import {User} from '../../class/User';
|
||||
|
||||
@Component({
|
||||
selector: 'c-admin-header',
|
||||
templateUrl: './admin-header.component.html',
|
||||
styleUrls: ['./admin-header.component.less']
|
||||
})
|
||||
export class AdminHeaderComponent implements OnInit {
|
||||
|
||||
constructor(private userService: GlobalUserService) {
|
||||
}
|
||||
|
||||
user: User
|
||||
@Output() infoClicked = new EventEmitter<void>()
|
||||
|
||||
logout = () => this.userService.logout();
|
||||
infoClickedEvent = () => this.infoClicked.emit();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userService.watchUserInfo({
|
||||
next: data => this.user = data.result,
|
||||
error: err => this.user = null,
|
||||
complete: null
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
12
src/app/components/footer/footer.component.html
Normal file
12
src/app/components/footer/footer.component.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="footer">
|
||||
<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>
|
||||
22
src/app/components/footer/footer.component.less
Normal file
22
src/app/components/footer/footer.component.less
Normal file
@@ -0,0 +1,22 @@
|
||||
.footer {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
background-color: #eeeeee;
|
||||
text-align: center;
|
||||
color: #009688;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
/* 设置z-index 是为了write页面将footer隐藏*/
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #ffffff;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #009688 !important;
|
||||
}
|
||||
25
src/app/components/footer/footer.component.spec.ts
Normal file
25
src/app/components/footer/footer.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FooterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
19
src/app/components/footer/footer.component.ts
Normal file
19
src/app/components/footer/footer.component.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ComponentStateService} from '../../services/component-state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrls: ['./footer.component.less']
|
||||
})
|
||||
export class FooterComponent implements OnInit {
|
||||
|
||||
constructor(public componentStateService: ComponentStateService) {
|
||||
}
|
||||
|
||||
readonly gName: string;
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
36
src/app/components/header/header.component.html
Normal file
36
src/app/components/header/header.component.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<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>
|
||||
167
src/app/components/header/header.component.less
Normal file
167
src/app/components/header/header.component.less
Normal file
@@ -0,0 +1,167 @@
|
||||
@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 {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 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%;
|
||||
margin-left: 20px;
|
||||
max-width: 100%;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
#nav {
|
||||
display: block;
|
||||
background: white;
|
||||
opacity: 0.5;
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
flex: none;
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/app/components/header/header.component.spec.ts
Normal file
25
src/app/components/header/header.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HeaderComponent } from './header.component';
|
||||
|
||||
describe('HeaderComponent', () => {
|
||||
let component: HeaderComponent;
|
||||
let fixture: ComponentFixture<HeaderComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HeaderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
142
src/app/components/header/header.component.ts
Normal file
142
src/app/components/header/header.component.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {windowWidthChange} from '../../utils/util';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {User} from '../../class/User';
|
||||
import {ComponentStateService} from '../../services/component-state.service';
|
||||
import {GlobalUserService} from '../../services/global-user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.less']
|
||||
})
|
||||
export class HeaderComponent implements OnInit {
|
||||
|
||||
constructor(private router: Router,
|
||||
public componentStateService: ComponentStateService,
|
||||
private userService: GlobalUserService) {
|
||||
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.getInfo();
|
||||
this.showList = window.innerWidth > this.mobileMaxWidth;
|
||||
this.changeLoginButtonV();
|
||||
// 监听宽度变化
|
||||
windowWidthChange(() => {
|
||||
this.showList = window.innerWidth > this.mobileMaxWidth;
|
||||
this.changeLoginButtonV();
|
||||
});
|
||||
// 订阅一级路由的变化
|
||||
componentStateService.watchRouterChange().subscribe(prefix => {
|
||||
if (prefix === '/user' || prefix === '/write' || prefix === '/update') {
|
||||
this.size = 'default';
|
||||
} else {
|
||||
this.size = 'large';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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.userService.watchUserInfo({
|
||||
complete: () => null,
|
||||
error: (err) => null,
|
||||
next: data => {
|
||||
this.userInfo = data.result;
|
||||
this.changeLoginButtonV();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.userService.logout({
|
||||
next: data => null,
|
||||
error: err => null,
|
||||
complete: () => null
|
||||
});
|
||||
}
|
||||
|
||||
toAdminPage() {
|
||||
this.router.navigateByUrl('/admin')
|
||||
}
|
||||
}
|
||||
|
||||
13
src/app/global-variables.less
Normal file
13
src/app/global-variables.less
Normal file
@@ -0,0 +1,13 @@
|
||||
// 定义less 中的全局变量
|
||||
// 使用的时候导入本文件
|
||||
|
||||
/** 移动端适配时的分割宽度 */
|
||||
@max-width: 940px;
|
||||
|
||||
|
||||
/**** header ****/
|
||||
// header的最小高度
|
||||
@header-min-height: 300px;
|
||||
@header-logo-width: 50px;
|
||||
@header-logo-height: 50px;
|
||||
/**** header ****/
|
||||
61
src/app/services/component-state.service.ts
Normal file
61
src/app/services/component-state.service.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {filter} from 'rxjs/operators';
|
||||
import {NavigationEnd, Router, RouterEvent} from '@angular/router';
|
||||
import {Observable, of, Subscriber} from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ComponentStateService {
|
||||
|
||||
constructor(private router: Router) {
|
||||
this.watchRouterChange()
|
||||
}
|
||||
|
||||
visible = {
|
||||
header: true,
|
||||
footer: true,
|
||||
globalBackToTop: true
|
||||
}
|
||||
|
||||
currentPath: string
|
||||
getCurrentRouterPath = () => this.currentPath;
|
||||
|
||||
watchRouterChange() {
|
||||
let subscriber: Subscriber<string>;
|
||||
const ob = new Observable(o => subscriber = o);
|
||||
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.dealWithPathChange(prefix)
|
||||
this.currentPath = prefix;
|
||||
if (subscriber) subscriber.next(prefix)
|
||||
});
|
||||
return ob;
|
||||
}
|
||||
|
||||
private dealWithPathChange(path) {
|
||||
// tslint:disable-next-line:forin
|
||||
for (const visibleKey in this.visible) {
|
||||
this.visible[visibleKey] = true
|
||||
}
|
||||
switch (path) {
|
||||
case '/admin':
|
||||
this.visible.header = false
|
||||
this.visible.footer = false
|
||||
this.visible.globalBackToTop = false
|
||||
break
|
||||
case '/user':
|
||||
case '/write':
|
||||
this.visible.footer = false
|
||||
this.visible.globalBackToTop = false
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
122
src/app/services/global-user.service.ts
Normal file
122
src/app/services/global-user.service.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {LoginReq, User} from '../class/User';
|
||||
import {ApiService} from '../api/api.service';
|
||||
import {Observable, Observer} from 'rxjs';
|
||||
import {Response} from '../class/HttpReqAndResp';
|
||||
import {LocalStorageService} from './local-storage.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GlobalUserService {
|
||||
|
||||
constructor(private apiService: ApiService,
|
||||
private localStorageService: LocalStorageService) {
|
||||
}
|
||||
|
||||
private lastRequestTime: number;
|
||||
|
||||
// 存储订阅者
|
||||
private userObserverArray: Observer<Response<User>>[] = [];
|
||||
|
||||
watchUserInfo(observer: Observer<Response<User>>) {
|
||||
if (this.userObserverArray.indexOf(observer) < 0) this.userObserverArray.push(observer);
|
||||
const user = this.localStorageService.getUser();
|
||||
// 判断本地缓存的用户信息是否符合要求,符合要求返回本地缓存
|
||||
if (this.localStorageService.isLogin() && user && !this.localStorageService.checkNeedNet()) {
|
||||
observer.next(new Response<User>(user));
|
||||
return {
|
||||
unsubscribe() {
|
||||
observer.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.lastRequestTime && Date.now() - this.lastRequestTime < 500) {
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
this.userObserverArray.splice(this.userObserverArray.indexOf(observer), 1);
|
||||
observer.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 不符合 请求网络数据并更新缓存
|
||||
// 向订阅者传数据
|
||||
this.lastRequestTime = Date.now();
|
||||
// 获取数据
|
||||
const subscription = this.getUserInfoFromServer();
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
this.userObserverArray.splice(this.userObserverArray.indexOf(observer), 1);
|
||||
observer.complete();
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新用户信息
|
||||
refreshUserInfo(): void {
|
||||
this.getUserInfoFromServer();
|
||||
}
|
||||
|
||||
login(loginReq: LoginReq, observer: Observer<Response<User>>) {
|
||||
const oob = new Observable<Response<User>>(o => observer = o);
|
||||
const subscription = this.apiService.login(loginReq).subscribe({
|
||||
next: o => {
|
||||
// 登录成功
|
||||
this.localStorageService.setToken(o.result.token);
|
||||
this.localStorageService.setUser(o.result);
|
||||
// this.userObserver.next(o);
|
||||
this.userObserverArray.forEach(ob => ob.next(o))
|
||||
observer.next(o);
|
||||
observer.complete();
|
||||
},
|
||||
error: err => {
|
||||
observer.error(err);
|
||||
observer.complete();
|
||||
}
|
||||
});
|
||||
return {
|
||||
unsubscribe() {
|
||||
observer.complete();
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
logout(observer?: Observer<Response<string>>) {
|
||||
// 如果不需要返回消息也ok
|
||||
this.apiService.logout().subscribe(data => {
|
||||
this.localStorageService.clear();
|
||||
this.userObserverArray.forEach(ob => ob.next(new Response<User>(null)))
|
||||
if (observer) {
|
||||
observer.next(data);
|
||||
observer.complete();
|
||||
}
|
||||
},
|
||||
error => {
|
||||
if (observer) {
|
||||
observer.error(error);
|
||||
observer.complete();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private getUserInfoFromServer() {
|
||||
return this.apiService.userInfo().subscribe({
|
||||
next: o => {
|
||||
this.localStorageService.setUser(o.result);
|
||||
this.userObserverArray.forEach(ob => ob.next(o));
|
||||
},
|
||||
error: err => {
|
||||
// console.debug('登录过期 token错误 等等');
|
||||
if (err.code === -1) {
|
||||
// 请求重复
|
||||
return
|
||||
}
|
||||
this.localStorageService.removeToken();
|
||||
this.userObserverArray.forEach(ob => ob.next(new Response<User>(null)));
|
||||
this.userObserverArray.forEach(ob => ob.error(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
63
src/app/services/local-storage.service.ts
Normal file
63
src/app/services/local-storage.service.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {User} from '../class/User';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LocalStorageService {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
// 30s
|
||||
readonly place = 30 * 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;
|
||||
}
|
||||
}
|
||||
9
src/app/utils/color.ts
Normal file
9
src/app/utils/color.ts
Normal 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'}, // 靛青
|
||||
];
|
||||
26
src/app/utils/dataUtil.ts
Normal file
26
src/app/utils/dataUtil.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {PageList} from '../class/HttpReqAndResp';
|
||||
|
||||
/**
|
||||
* 判断 一个Page<any>[] 中是否存在一条已查询的数据
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 单页数量
|
||||
* @param pageList 源数据
|
||||
* @return 未查到:null 查到:该条数据
|
||||
*/
|
||||
export function exist<T>(pageNum: number, pageSize: number, pageList: PageList<any>[]): Observable<PageList<T>> | null {
|
||||
if (pageList === undefined || pageList == null || pageList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < pageList.length; i++) {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
if (pageList[i].pageNum == pageNum && pageList[i].pageSize == pageSize) {
|
||||
const ob: Observable<PageList<T>> = new Observable(o => {
|
||||
o.next(pageList[i]);
|
||||
o.complete();
|
||||
})
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
11
src/app/utils/svgIconUtil.ts
Normal file
11
src/app/utils/svgIconUtil.ts
Normal 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>';
|
||||
}
|
||||
17
src/app/utils/util.ts
Normal file
17
src/app/utils/util.ts
Normal 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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<div class="inner-content">
|
||||
<nz-card nzTitle="文章管理" nzSize="small">
|
||||
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="page"
|
||||
[nzPageSize]="pageSize" [nzLoading]="loading"
|
||||
(nzPageIndexChange)="getArticle()" nzFrontPagination="false">
|
||||
<thead>
|
||||
<th>标题</th>
|
||||
<th>发布日期</th>
|
||||
<th>更新日期</th>
|
||||
<th>文章类型</th>
|
||||
<th>阅读量</th>
|
||||
<th>是否可见</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of table.data">
|
||||
<td nz-typography nzEllipsis="true" [nzTooltipTitle]="data.title" nzTooltipPlacement="right"
|
||||
nz-tooltip>{{data.title}}</td>
|
||||
<td>{{data.publishDateFormat}}</td>
|
||||
<td>{{data.updateDateFormat}}</td>
|
||||
<td>
|
||||
<nz-tag nzColor="green" *ngIf="data.original">原创</nz-tag>
|
||||
<nz-tag nzColor="#ff5500" *ngIf="!data.original">转载</nz-tag>
|
||||
</td>
|
||||
<td>
|
||||
<nz-tag [nzColor]="'purple'">{{data.readingNumber}}</nz-tag>
|
||||
</td>
|
||||
<td><input type="checkbox" [checked]="data.open" disabled></td>
|
||||
<td>
|
||||
<a routerLink="/write" [queryParams]="{id:data.id}" class="edit-opr">编辑</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a [routerLink]="'/article'+data.id" class="show-opr">查看</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a nz-popconfirm nzPopconfirmTitle="确定要删除这篇文章吗?" nzOkText="删除" nzCancelText="点错了"
|
||||
(nzOnConfirm)="deleteArticle(data.id)" class="del-opr">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
</nz-card>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
td {
|
||||
max-width: 200px;
|
||||
}
|
||||
50
src/app/view/admin/admin-article/admin-article.component.ts
Normal file
50
src/app/view/admin/admin-article/admin-article.component.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {ApiService} from '../../../api/api.service';
|
||||
import {PageList} from '../../../class/HttpReqAndResp';
|
||||
import {Article} from '../../../class/Article';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-article',
|
||||
templateUrl: './admin-article.component.html',
|
||||
styleUrls: ['./admin-article.component.less']
|
||||
})
|
||||
export class AdminArticleComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private nzMessage: NzMessageService, private title: Title) {
|
||||
}
|
||||
|
||||
page: number = 1;
|
||||
pageSize: number = 10;
|
||||
|
||||
pageList: PageList<Article> = new PageList<Article>();
|
||||
|
||||
loading: boolean = true;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.title.setTitle('小海博客 | 文章管理')
|
||||
this.getArticle();
|
||||
}
|
||||
|
||||
getArticle = () => this.apiService.adminArticles(this.page, this.pageSize).subscribe({
|
||||
next: data => this.pageList = data.result,
|
||||
complete: () => this.loading = false,
|
||||
error: err => this.loading = false
|
||||
})
|
||||
|
||||
deleteArticle(id) {
|
||||
this.loading = true;
|
||||
this.apiService.deleteArticle(id).subscribe({
|
||||
next: data => {
|
||||
this.nzMessage.success('删除成功')
|
||||
this.loading = false;
|
||||
this.getArticle();
|
||||
},
|
||||
error: err => {
|
||||
this.nzMessage.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
31
src/app/view/admin/admin-article/admin-article.module.ts
Normal file
31
src/app/view/admin/admin-article/admin-article.module.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AdminArticleComponent} from './admin-article.component';
|
||||
import {
|
||||
NzCardModule,
|
||||
NzDividerModule,
|
||||
NzPopconfirmModule,
|
||||
NzTableModule, NzTagModule,
|
||||
NzToolTipModule,
|
||||
NzTypographyModule
|
||||
} from 'ng-zorro-antd';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AdminArticleComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{path: '', component: AdminArticleComponent}]),
|
||||
NzTableModule,
|
||||
NzTypographyModule,
|
||||
NzToolTipModule,
|
||||
NzCardModule,
|
||||
NzDividerModule,
|
||||
NzPopconfirmModule,
|
||||
NzTagModule,
|
||||
]
|
||||
})
|
||||
export class AdminArticleModule {
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<div class="inner-content">
|
||||
<nz-card nzTitle="评论管理" nzSize="small">
|
||||
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
|
||||
[nzPageSize]="pageSize" [nzLoading]="loading"
|
||||
(nzPageIndexChange)="getComment()" nzFrontPagination="false">
|
||||
<thead>
|
||||
<th>文章标题</th>
|
||||
<th>评论内容</th>
|
||||
<th>评论者</th>
|
||||
<th>评论日期</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of table.data">
|
||||
<td nz-typography nzEllipsis="true" [nzTooltipTitle]="data.articleTitle" nzTooltipPlacement="right"
|
||||
nz-tooltip>{{data.articleTitle}}</td>
|
||||
<td nz-typography nzEllipsis="true" [nzTooltipTitle]="data.content" nzTooltipPlacement="right"
|
||||
nz-tooltip style="min-width: 100px;max-width: 400px">
|
||||
<span *ngIf="!editInfo.editFocus||data.id!==editInfo.id">{{data.content}}</span>
|
||||
<nz-input-group *ngIf="editInfo.editFocus&&data.id===editInfo.id"
|
||||
[nzPrefix]="tagIcon" style="width: 50%" (blur)="editInfo.editFocus=false">
|
||||
<input type="text" nz-input [(ngModel)]="editInfo.content.content" nzSize="small"
|
||||
[autofocus]="editInfo.editFocus&&data.id===editInfo.id"
|
||||
(keyup.enter)="edit()">
|
||||
<button nz-button (click)="edit()" nzSize="small">更新</button>
|
||||
<button nz-button (click)="editInfo.editFocus=false" nzSize="small">取消</button>
|
||||
</nz-input-group>
|
||||
</td>
|
||||
<td>{{data.authorName}}</td>
|
||||
<td>{{data.date}}</td>
|
||||
<td>
|
||||
<a (click)="editFocus(data)" class="edit-opr">编辑</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a nz-popconfirm nzPopconfirmTitle="确定要删除这篇文章吗?" nzOkText="删除" nzCancelText="点错了"
|
||||
(nzOnConfirm)="deleteComment(data.id)" class="del-opr">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
</nz-card>
|
||||
</div>
|
||||
<ng-template #tagIcon><i nz-icon nzType="message" nzTheme="outline"></i></ng-template>
|
||||
@@ -0,0 +1,3 @@
|
||||
td {
|
||||
max-width: 300px;
|
||||
}
|
||||
102
src/app/view/admin/admin-comment/admin-comment.component.ts
Normal file
102
src/app/view/admin/admin-comment/admin-comment.component.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {ApiService} from '../../../api/api.service';
|
||||
import {PageList} from '../../../class/HttpReqAndResp';
|
||||
import {Comment, CommentReq} from '../../../class/Comment';
|
||||
import {GlobalUserService} from '../../../services/global-user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-comment',
|
||||
templateUrl: './admin-comment.component.html',
|
||||
styleUrls: ['./admin-comment.component.less']
|
||||
})
|
||||
export class AdminCommentComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private messageService: NzMessageService, private userService: GlobalUserService) {
|
||||
this.userService.watchUserInfo({
|
||||
next: data => {
|
||||
if (data.result) {
|
||||
if (data.result.role === 'admin') {
|
||||
this.getComment = this.getCommentForAdmin;
|
||||
} else {
|
||||
this.getComment = this.getCommentForUser;
|
||||
}
|
||||
} else {
|
||||
this.getComment = this.getCommentForUser;
|
||||
}
|
||||
this.getComment()
|
||||
},
|
||||
error: null,
|
||||
complete: null
|
||||
})
|
||||
}
|
||||
|
||||
loading: boolean = true;
|
||||
pageIndex: number = 1;
|
||||
pageSize: number = 10;
|
||||
pageList: PageList<Comment> = new PageList<Comment>();
|
||||
editInfo = {
|
||||
id: null,
|
||||
content: new CommentReq(true),
|
||||
editFocus: false,
|
||||
}
|
||||
getComment: any;// 存放获取评论的方法
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
|
||||
getCommentForAdmin = () => this.apiService.getCommentByTypeForAdmin(true, this.pageIndex, this.pageSize).subscribe({
|
||||
next: data => this.pageList = data.result,
|
||||
complete: () => this.loading = false,
|
||||
error: err => this.loading = false
|
||||
})
|
||||
|
||||
getCommentForUser = () => this.apiService.getCommentByTypeForUser(true, this.pageIndex, this.pageSize).subscribe({
|
||||
next: data => this.pageList = data.result,
|
||||
complete: () => this.loading = false,
|
||||
error: err => this.loading = false
|
||||
})
|
||||
|
||||
deleteComment(id: number) {
|
||||
this.loading = true;
|
||||
this.apiService.deleteComment(id).subscribe({
|
||||
next: () => {
|
||||
this.messageService.success('删除评论成功');
|
||||
this.getComment();
|
||||
},
|
||||
error: err => {
|
||||
this.loading = false;
|
||||
this.messageService.error(err.msg);
|
||||
},
|
||||
complete: () => this.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
edit() {
|
||||
this.editInfo.editFocus = false;
|
||||
this.loading = true;
|
||||
this.apiService.updateComment(this.editInfo.content).subscribe({
|
||||
next: data => {
|
||||
this.messageService.success('更新评论成功');
|
||||
this.getComment();
|
||||
},
|
||||
error: err => {
|
||||
this.loading = false;
|
||||
this.messageService.success(err.msg);
|
||||
},
|
||||
complete: () => this.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
editFocus(data: Comment) {
|
||||
this.editInfo.id = data.id;
|
||||
this.editInfo.content.content = data.content;
|
||||
this.editInfo.content.id = data.id;
|
||||
this.editInfo.content.articleID = data.articleID;
|
||||
this.editInfo.content.pid = data.pid;
|
||||
this.editInfo.content.responseId = data.responseId;
|
||||
this.editInfo.editFocus = true;
|
||||
}
|
||||
}
|
||||
37
src/app/view/admin/admin-comment/admin-comment.module.ts
Normal file
37
src/app/view/admin/admin-comment/admin-comment.module.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AdminCommentComponent} from './admin-comment.component';
|
||||
import {
|
||||
NzButtonModule,
|
||||
NzCardModule,
|
||||
NzDividerModule, NzIconModule, NzInputModule,
|
||||
NzPopconfirmModule,
|
||||
NzTableModule,
|
||||
NzToolTipModule,
|
||||
NzTypographyModule
|
||||
} from 'ng-zorro-antd';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AdminCommentComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{path: '', component: AdminCommentComponent}]),
|
||||
NzCardModule,
|
||||
NzTableModule,
|
||||
NzDividerModule,
|
||||
NzPopconfirmModule,
|
||||
NzTypographyModule,
|
||||
NzToolTipModule,
|
||||
NzInputModule,
|
||||
FormsModule,
|
||||
NzIconModule,
|
||||
NzButtonModule
|
||||
]
|
||||
})
|
||||
export class AdminCommentModule {
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<div *ngIf="userInfo&&userInfo.role==='admin'">
|
||||
<div nz-row>
|
||||
<nz-card nzTitle="统计" nz-col nzSpan="12" nzSize="small">
|
||||
<nz-row [nzGutter]="24">
|
||||
<nz-col [nzSpan]="6">
|
||||
<nz-statistic [nzValue]="(counts.articleCount | number)!" nzTitle="文章数量"></nz-statistic>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="6">
|
||||
<nz-statistic [nzValue]="(counts.tagCount | number)!" nzTitle="标签量"></nz-statistic>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="6">
|
||||
<nz-statistic [nzValue]="(counts.categoryCount | number)!" nzTitle="分类数量"></nz-statistic>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="6">
|
||||
<nz-statistic [nzValue]="(counts.commentCount | number)!" nzTitle="评论量"></nz-statistic>
|
||||
</nz-col>
|
||||
</nz-row>
|
||||
</nz-card>
|
||||
<nz-card nzTitle="信息" nz-col nzSpan="11" nzOffset="1" nzSize="small">
|
||||
<nz-row [nzGutter]="24">
|
||||
<nz-col [nzSpan]="8">
|
||||
<nz-statistic [nzValue]="(counts.visitorCount | number)!" nzTitle="总访问量"></nz-statistic>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="8">
|
||||
<nz-statistic [nzValue]="(dayVisitCount | number)!" nzTitle="日访问量"></nz-statistic>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="8">
|
||||
<nz-statistic [nzValue]="userInfo.recentlyLandedDate?userInfo.recentlyLandedDate:''"
|
||||
nzTitle="上次登录"></nz-statistic>
|
||||
</nz-col>
|
||||
</nz-row>
|
||||
</nz-card>
|
||||
</div>
|
||||
<div nz-row style="margin-top: 30px">
|
||||
<nz-card style="width:100%;" nzSize="small" nzTitle="日志" [nzExtra]="reload">
|
||||
<ng-template #reload>
|
||||
<a (click)="getLog()" title="刷新"><i nz-icon nzType="reload" nzTheme="outline"></i></a>
|
||||
</ng-template>
|
||||
<nz-spin [nzSpinning]="logLoading" style="width: 100%;">
|
||||
<pre style="width: 100%;max-height: 500px;overflow: auto;">{{logText}}</pre>
|
||||
</nz-spin>
|
||||
</nz-card>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="userInfo&&userInfo.role==='user'">
|
||||
<div nz-row>
|
||||
<nz-card nzTitle="信息" nz-col nzSpan="6" nzOffset="1" nzSize="small">
|
||||
<nz-statistic [nzValue]="userInfo.recentlyLandedDate?userInfo.recentlyLandedDate:''"
|
||||
nzTitle="上次登录"></nz-statistic>
|
||||
</nz-card>
|
||||
<nz-card nzTitle="关于此博客" nzSize="small" nz-col nzSpan="6" nzOffset="2">
|
||||
<p>此博客由 <a href="https://github.com/xiaohai2271" target="_blank">禾几海(郑海)</a> 设计并实现的</p>
|
||||
<p>博客自2019年3月开始开发编写 5月开始正式运行至今</p>
|
||||
<p>博客所有代码都是开源的,你可以随意修改,运行和发布</p>
|
||||
<p>
|
||||
<a href="https://github.com/xiaohai2271/blog-backEnd" target="_blank">后端代码可以在此处找到</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a href="https://github.com/xiaohai2271/blog-frontEnd" target="_blank">前端代码可以在此处找到</a>
|
||||
</p>
|
||||
<p>如果觉得博客还不错,请前往Github支持我,给我点一个star吧</p>
|
||||
</nz-card>
|
||||
<nz-card nz-col nzSpan="6" nzOffset="2">
|
||||
<p style="font-style: italic">坚强的信心,能使平凡的人做出惊人的事业。</p>
|
||||
<p style="text-align: right"> ——马尔顿</p>
|
||||
</nz-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,61 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {ApiService} from '../../../api/api.service';
|
||||
import {GlobalUserService} from '../../../services/global-user.service';
|
||||
import {User} from '../../../class/User';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-dashboard',
|
||||
templateUrl: './admin-dashboard.component.html',
|
||||
styleUrls: ['./admin-dashboard.component.less']
|
||||
})
|
||||
export class AdminDashboardComponent implements OnInit {
|
||||
constructor(private apiService: ApiService, private userService: GlobalUserService, private http: HttpClient) {
|
||||
this.getUserInfo();
|
||||
}
|
||||
|
||||
logLoading: boolean = true;
|
||||
logText: string = null;
|
||||
counts: {
|
||||
articleCount: number,
|
||||
visitorCount: number,
|
||||
categoryCount: number,
|
||||
leaveMsgCount: number,
|
||||
tagCount: number,
|
||||
commentCount: number
|
||||
} = {articleCount: 0, visitorCount: 0, categoryCount: 0, tagCount: 0, commentCount: 0, leaveMsgCount: 0}
|
||||
|
||||
dayVisitCount: number = 0;
|
||||
userInfo: User = new User();
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
getLog() {
|
||||
this.http.get('https://api.celess.cn/blog.log', {responseType: 'text'}).subscribe(data => {
|
||||
this.logText = data;
|
||||
this.logLoading = false
|
||||
});
|
||||
}
|
||||
|
||||
getCounts = () => this.apiService.counts().subscribe({
|
||||
next: data => this.counts = data.result
|
||||
})
|
||||
|
||||
getDayVisitCount = () => this.apiService.dayVisitCount().subscribe({
|
||||
next: data => this.dayVisitCount = data.result
|
||||
})
|
||||
|
||||
getUserInfo = () => this.userService.watchUserInfo({
|
||||
next: data => {
|
||||
this.userInfo = data.result
|
||||
if (data.result && data.result.role === 'admin') {
|
||||
this.getLog();
|
||||
this.getCounts();
|
||||
this.getDayVisitCount();
|
||||
}
|
||||
},
|
||||
error: null,
|
||||
complete: null
|
||||
})
|
||||
}
|
||||
31
src/app/view/admin/admin-dashboard/admin-dashboard.module.ts
Normal file
31
src/app/view/admin/admin-dashboard/admin-dashboard.module.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AdminDashboardComponent} from './admin-dashboard.component';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {
|
||||
NzButtonModule,
|
||||
NzCardModule,
|
||||
NzDividerModule,
|
||||
NzGridModule,
|
||||
NzIconModule,
|
||||
NzSpinModule,
|
||||
NzStatisticModule
|
||||
} from 'ng-zorro-antd';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [AdminDashboardComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{path: '', component: AdminDashboardComponent}]),
|
||||
NzGridModule,
|
||||
NzCardModule,
|
||||
NzButtonModule,
|
||||
NzSpinModule,
|
||||
NzIconModule,
|
||||
NzStatisticModule,
|
||||
NzDividerModule
|
||||
]
|
||||
})
|
||||
export class AdminDashboardModule {
|
||||
}
|
||||
62
src/app/view/admin/admin-link/admin-link.component.html
Normal file
62
src/app/view/admin/admin-link/admin-link.component.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<div class="inner-content">
|
||||
<nz-card nzTitle="友链管理" nzSize="small">
|
||||
<button nz-button (click)="addLink()" style="margin-bottom: 15px">新增</button>
|
||||
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
|
||||
[nzPageSize]="pageSize" [nzLoading]="loading"
|
||||
(nzPageIndexChange)="getLinks()" nzFrontPagination="false">
|
||||
<thead>
|
||||
<th>友链名称</th>
|
||||
<th>友链地址</th>
|
||||
<th>是否可见</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of table.data">
|
||||
<td>{{data.name}}</td>
|
||||
<td><a [href]="data.url" target="_blank">{{data.url}}</a></td>
|
||||
<td>
|
||||
<input type="checkbox" disabled [checked]="data.open">
|
||||
</td>
|
||||
<td>
|
||||
<a (click)="showEdit(data)" class="edit-opr">编辑</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a nz-popconfirm nzPopconfirmTitle="确定要删除这条友链吗?" nzOkText="删除" nzCancelText="点错了"
|
||||
(nzOnConfirm)="delete(data.id)" class="del-opr">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
</nz-card>
|
||||
</div>
|
||||
|
||||
<nz-modal [(nzVisible)]="modalVisible" [nzTitle]="modalTitle" (nzOnOk)="modalConfirm()"
|
||||
(nzOnCancel)="modalVisible = false" [nzClosable]="true" [nzOkDisabled]="!formGroup.valid">
|
||||
<form nz-form [formGroup]="formGroup">
|
||||
<nz-form-item>
|
||||
<nz-form-label nzRequired>网站名称</nz-form-label>
|
||||
<nz-form-control nzErrorTip="网站名称不可为空">
|
||||
<input nz-input formControlName="name">
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label nzRequired>网站链接</nz-form-label>
|
||||
<nz-form-control [nzErrorTip]="nameErrTip">
|
||||
<input nz-input formControlName="url">
|
||||
<ng-template #nameErrTip>
|
||||
<div *ngIf="formGroup.controls.url.hasError('required')">网站链接不可为空</div>
|
||||
<div *ngIf="formGroup.controls.url.hasError('pattern')">网站链接格式不正确</div>
|
||||
<div></div>
|
||||
</ng-template>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label nzRequired>是否公开</nz-form-label>
|
||||
<nz-form-control nzErrorTip="不可为空">
|
||||
<nz-select nzPlaceHolder="请选择" formControlName="open" [nzAllowClear]="true">
|
||||
<nz-option [nzValue]="true" nzLabel="公开"></nz-option>
|
||||
<nz-option [nzValue]="false" nzLabel="不公开"></nz-option>
|
||||
</nz-select>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</form>
|
||||
</nz-modal>
|
||||
91
src/app/view/admin/admin-link/admin-link.component.ts
Normal file
91
src/app/view/admin/admin-link/admin-link.component.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {PageList, Response} from '../../../class/HttpReqAndResp';
|
||||
import {Link} from '../../../class/Link';
|
||||
import {ApiService} from '../../../api/api.service';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {FormControl, FormGroup, Validators} from '@angular/forms';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-link',
|
||||
templateUrl: './admin-link.component.html',
|
||||
styleUrls: ['./admin-link.component.less']
|
||||
})
|
||||
export class AdminLinkComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private messageService: NzMessageService) {
|
||||
this.formGroup = new FormGroup({
|
||||
id: new FormControl(null),
|
||||
name: new FormControl(null, [Validators.required]),
|
||||
url: new FormControl(null, [Validators.required, Validators.pattern(/^(https:\/\/|http:\/\/|)([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$/)]),
|
||||
open: new FormControl(null, [Validators.required]),
|
||||
oper: new FormControl(null)
|
||||
})
|
||||
}
|
||||
|
||||
pageList: PageList<Link> = new PageList<Link>();
|
||||
loading: boolean = true;
|
||||
pageIndex: number = 1;
|
||||
pageSize: number = 10;
|
||||
modalVisible: boolean = false;
|
||||
modalTitle: string = '';
|
||||
formGroup: FormGroup;
|
||||
|
||||
getLinks = () => this.apiService.adminLinks(this.pageSize, this.pageIndex).subscribe({
|
||||
next: data => this.pageList = data.result,
|
||||
error: () => this.loading = false,
|
||||
complete: () => this.loading = false,
|
||||
})
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getLinks();
|
||||
}
|
||||
|
||||
delete(id: number) {
|
||||
this.apiService.deleteLink(id).subscribe({
|
||||
next: data => {
|
||||
this.messageService.success('删除成功');
|
||||
this.getLinks();
|
||||
},
|
||||
error: () => {
|
||||
this.loading = false;
|
||||
this.messageService.error('删除失败');
|
||||
},
|
||||
complete: () => this.loading = false,
|
||||
})
|
||||
}
|
||||
|
||||
showEdit(data: Link) {
|
||||
this.modalVisible = true;
|
||||
this.modalTitle = '编辑友链信息';
|
||||
this.formGroup.patchValue(data);
|
||||
this.formGroup.controls.oper.setValue('edit')
|
||||
}
|
||||
|
||||
modalConfirm() {
|
||||
this.modalVisible = false;
|
||||
const linkReq: Link = new Link();
|
||||
linkReq.name = this.formGroup.value.name;
|
||||
linkReq.url = this.formGroup.value.url;
|
||||
linkReq.open = this.formGroup.value.open;
|
||||
const oper = this.formGroup.value.oper;
|
||||
let observable: Observable<Response<Link>>;
|
||||
if (oper === 'edit') {
|
||||
linkReq.id = this.formGroup.value.id;
|
||||
observable = this.apiService.updateLink(linkReq);
|
||||
} else if (oper === 'add') {
|
||||
observable = this.apiService.createLink(linkReq);
|
||||
}
|
||||
observable.subscribe({
|
||||
next: data => this.messageService.success('操作成功'),
|
||||
error: err => this.messageService.success('操作失败,', err.msg),
|
||||
complete: () => this.getLinks()
|
||||
})
|
||||
}
|
||||
|
||||
addLink() {
|
||||
this.modalVisible = true;
|
||||
this.modalTitle = '新增友链信息';
|
||||
this.formGroup.controls.oper.setValue('add')
|
||||
}
|
||||
}
|
||||
37
src/app/view/admin/admin-link/admin-link.module.ts
Normal file
37
src/app/view/admin/admin-link/admin-link.module.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AdminLinkComponent} from './admin-link.component';
|
||||
import {
|
||||
NzButtonModule,
|
||||
NzCardModule,
|
||||
NzDividerModule,
|
||||
NzFormModule, NzInputModule,
|
||||
NzModalModule,
|
||||
NzPopconfirmModule, NzSelectModule,
|
||||
NzTableModule
|
||||
} from 'ng-zorro-antd';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AdminLinkComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{path: '', component: AdminLinkComponent}]),
|
||||
NzCardModule,
|
||||
NzTableModule,
|
||||
NzDividerModule,
|
||||
NzPopconfirmModule,
|
||||
NzModalModule,
|
||||
NzFormModule,
|
||||
ReactiveFormsModule,
|
||||
NzInputModule,
|
||||
NzSelectModule,
|
||||
NzButtonModule
|
||||
]
|
||||
})
|
||||
export class AdminLinkModule {
|
||||
}
|
||||
65
src/app/view/admin/admin-routing.module.ts
Normal file
65
src/app/view/admin/admin-routing.module.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {AdminComponent} from './admin.component';
|
||||
import {AuthGuard} from './auth.guard';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AdminComponent,
|
||||
// canActivate: [AuthGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'article',
|
||||
loadChildren: () => import('./admin-article/admin-article.module').then(mod => mod.AdminArticleModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'comment',
|
||||
loadChildren: () => import('./admin-comment/admin-comment.module').then(mod => mod.AdminCommentModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'link',
|
||||
loadChildren: () => import('./admin-link/admin-link.module').then(mod => mod.AdminLinkModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'tag',
|
||||
loadChildren: () => import('./admin-tag/admin-tag.module').then(mod => mod.AdminTagModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'update',
|
||||
loadChildren: () => import('./admin-update/admin-update.module').then(mod => mod.AdminUpdateModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
loadChildren: () => import('./admin-user/admin-user.module').then(mod => mod.AdminUserModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'visitor',
|
||||
loadChildren: () => import('./admin-visitor/admin-visitor.module').then(mod => mod.AdminVisitorModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
loadChildren: () => import('./admin-dashboard/admin-dashboard.module').then(mod => mod.AdminDashboardModule),
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
|
||||
export class AdminRoutingModule {
|
||||
}
|
||||
92
src/app/view/admin/admin-tag/admin-tag.component.html
Normal file
92
src/app/view/admin/admin-tag/admin-tag.component.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<div class="inner-content">
|
||||
<nz-card nzTitle="" nzSize="small">
|
||||
<nz-tabset>
|
||||
<nz-tab nzTitle="分类管理" (nzClick)="editInfo.editFocus=false">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<nz-input-group *ngIf="editInfo.editFocus&&editInfo.isAdd" [nzPrefix]="tagIcon"
|
||||
style="width: 200px">
|
||||
<input type="text" nz-input [(ngModel)]="editInfo.name" (keyup.enter)="addCategory()"
|
||||
[autofocus]="editInfo.editFocus"
|
||||
(blur)="editInfo.editFocus=false">
|
||||
</nz-input-group>
|
||||
<button nz-button (click)="addCategory()">新增分类</button>
|
||||
<button nz-button (click)="editInfo.editFocus=false" *ngIf="editInfo.editFocus&&editInfo.isAdd">取消
|
||||
</button>
|
||||
</div>
|
||||
<nz-table #table [nzData]="categoryList" [nzTotal]="categoryList.length"
|
||||
(nzPageIndexChange)="getCategory()"
|
||||
nzFrontPagination="false" [nzLoading]="loading">
|
||||
<thead>
|
||||
<th>分类名</th>
|
||||
<th>分类文章数量</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of table.data">
|
||||
<td>
|
||||
<span *ngIf="!editInfo.editFocus||data.id!==editInfo.id">{{data.name}}</span>
|
||||
<nz-input-group *ngIf="!editInfo.isAdd&&editInfo.editFocus&&data.id===editInfo.id"
|
||||
[nzPrefix]="tagIcon" style="width: 300px">
|
||||
<input type="text" nz-input [(ngModel)]="editInfo.name" nzSize="small"
|
||||
[autofocus]="editInfo.editFocus&&data.id===editInfo.id"
|
||||
(keyup.enter)="edit('category')" (blur)="editInfo.editFocus=false">
|
||||
<button nz-button (click)="edit('category')" nzSize="small">更新</button>
|
||||
<button nz-button (click)="editInfo.editFocus=false" nzSize="small">取消</button>
|
||||
</nz-input-group>
|
||||
</td>
|
||||
<td>
|
||||
<nz-tag [nzColor]="'purple'">{{data.articles ? data.articles.length : 0}}</nz-tag>
|
||||
</td>
|
||||
<td>
|
||||
<a (click)="editFocus(data)" class="edit-opr">编辑</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a [routerLink]="'/categories/'+data.name" class="show-opr">查看</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a nz-popconfirm nzPopconfirmTitle="确定要删除这个分类吗?" nzOkText="删除" nzCancelText="点错了"
|
||||
(nzOnConfirm)="delete(data.id,'category')" class="del-opr">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
</nz-tab>
|
||||
<nz-tab nzTitle="标签管理" (nzClick)="editInfo.editFocus=false">
|
||||
<nz-table #tagTable [nzData]="tagPageList.list" [nzTotal]="tagPageList.total"
|
||||
[(nzPageIndex)]="pageIndex"
|
||||
[nzPageSize]="pageSize" (nzPageIndexChange)="getTag()" nzFrontPagination="false">
|
||||
<thead>
|
||||
<th>标签名</th>
|
||||
<th>分类文章数量</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of tagTable.data">
|
||||
<td>
|
||||
<span *ngIf="!editInfo.editFocus||data.id!==editInfo.id">{{data.name}}</span>
|
||||
<nz-input-group *ngIf="!editInfo.isAdd&&editInfo.editFocus&&data.id===editInfo.id"
|
||||
[nzPrefix]="tagIcon" style="width: 300px">
|
||||
<input type="text" nz-input [(ngModel)]="editInfo.name" nzSize="small"
|
||||
[autofocus]="editInfo.editFocus&&data.id===editInfo.id"
|
||||
(keyup.enter)="edit('tag')" (blur)="editInfo.editFocus=false">
|
||||
<button nz-button (click)="edit('tag')" nzSize="small">更新</button>
|
||||
<button nz-button (click)="editInfo.editFocus=false" nzSize="small">取消</button>
|
||||
</nz-input-group>
|
||||
</td>
|
||||
<td>
|
||||
<nz-tag [nzColor]="'purple'">{{data.articles ? data.articles.length : 0}}</nz-tag>
|
||||
</td>
|
||||
<td>
|
||||
<a (click)="editFocus(data)" class="edit-opr">编辑</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a [routerLink]="'/tags/'+data.name" class="show-opr">查看</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a nz-popconfirm nzPopconfirmTitle="确定要删除这个标签吗?" nzOkText="删除" nzCancelText="点错了"
|
||||
(nzOnConfirm)="delete(data.id,'tag')" class="del-opr">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
</nz-tab>
|
||||
</nz-tabset>
|
||||
</nz-card>
|
||||
</div>
|
||||
<ng-template #tagIcon><i nz-icon nzType="tag" nzTheme="outline"></i></ng-template>
|
||||
141
src/app/view/admin/admin-tag/admin-tag.component.ts
Normal file
141
src/app/view/admin/admin-tag/admin-tag.component.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {Category, Tag} from '../../../class/Tag';
|
||||
import {ApiService} from '../../../api/api.service';
|
||||
import {PageList} from '../../../class/HttpReqAndResp';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-tag',
|
||||
templateUrl: './admin-tag.component.html',
|
||||
styleUrls: ['./admin-tag.component.less']
|
||||
})
|
||||
export class AdminTagComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private nzMessageService: NzMessageService, private title: Title) {
|
||||
}
|
||||
|
||||
loading: boolean = true;
|
||||
|
||||
categoryList: Category[] = [];
|
||||
editInfo = {
|
||||
id: null,
|
||||
name: null,
|
||||
editFocus: false,
|
||||
isAdd: false
|
||||
}
|
||||
tagPageList: PageList<Tag> = new PageList<Tag>();
|
||||
|
||||
pageIndex: number = 1;
|
||||
pageSize: number = 10;
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.title.setTitle('小海博客 | 标签分类管理')
|
||||
this.getCategory();
|
||||
this.getTag();
|
||||
}
|
||||
|
||||
getCategory = () => this.apiService.categories().subscribe({
|
||||
next: data => this.categoryList = data.result,
|
||||
complete: () => this.loading = false,
|
||||
error: err => this.loading = false
|
||||
})
|
||||
|
||||
getTag = () => this.apiService.tags(this.pageIndex, this.pageSize).subscribe({
|
||||
next: data => this.tagPageList = data.result,
|
||||
complete: () => this.loading = false,
|
||||
error: err => this.loading = false
|
||||
})
|
||||
|
||||
delete(id, mode: 'tag' | 'category') {
|
||||
this.loading = true;
|
||||
if (mode === 'tag') {
|
||||
this.apiService.deleteTag(id).subscribe({
|
||||
next: data => {
|
||||
this.nzMessageService.success('删除成功')
|
||||
this.getTag();
|
||||
},
|
||||
complete: () => this.loading = false,
|
||||
error: err => {
|
||||
this.nzMessageService.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
} else if (mode === 'category') {
|
||||
this.apiService.deleteCategory(id).subscribe({
|
||||
next: data => {
|
||||
this.nzMessageService.success('删除成功')
|
||||
this.getCategory();
|
||||
},
|
||||
complete: () => this.loading = false,
|
||||
error: err => {
|
||||
this.nzMessageService.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
editFocus(data: Category) {
|
||||
this.editInfo.isAdd = false;
|
||||
this.editInfo.id = data.id;
|
||||
this.editInfo.name = data.name;
|
||||
this.editInfo.editFocus = true;
|
||||
}
|
||||
|
||||
edit(mode: 'tag' | 'category') {
|
||||
this.loading = true;
|
||||
if (mode === 'tag') {
|
||||
this.apiService.updateTag(this.editInfo.id, this.editInfo.name).subscribe({
|
||||
next: data => {
|
||||
this.nzMessageService.success('更新成功')
|
||||
this.getTag();
|
||||
},
|
||||
complete: () => this.loading = false,
|
||||
error: err => {
|
||||
this.nzMessageService.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
} else if (mode === 'category') {
|
||||
this.apiService.updateCategory(this.editInfo.id, this.editInfo.name).subscribe({
|
||||
next: data => {
|
||||
this.nzMessageService.success('更新成功')
|
||||
this.getCategory();
|
||||
},
|
||||
complete: () => this.loading = false,
|
||||
error: err => {
|
||||
this.nzMessageService.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
this.editInfo.editFocus = false
|
||||
this.editInfo.name = null
|
||||
}
|
||||
|
||||
addCategory() {
|
||||
this.editInfo.isAdd = true
|
||||
if (!this.editInfo.editFocus && this.editInfo.isAdd) {
|
||||
this.editInfo.name = null;
|
||||
this.editInfo.id = null;
|
||||
this.editInfo.editFocus = true;
|
||||
return
|
||||
}
|
||||
this.apiService.createCategory(this.editInfo.name).subscribe({
|
||||
next: data => {
|
||||
this.nzMessageService.success('新增成功')
|
||||
this.getCategory();
|
||||
},
|
||||
complete: () => this.loading = false,
|
||||
error: err => {
|
||||
this.nzMessageService.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
});
|
||||
this.editInfo.editFocus = false
|
||||
this.editInfo.isAdd = false
|
||||
this.editInfo.name = null
|
||||
}
|
||||
}
|
||||
35
src/app/view/admin/admin-tag/admin-tag.module.ts
Normal file
35
src/app/view/admin/admin-tag/admin-tag.module.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AdminTagComponent} from './admin-tag.component';
|
||||
import {
|
||||
NzButtonModule,
|
||||
NzCardModule,
|
||||
NzDividerModule, NzIconModule,
|
||||
NzInputModule, NzPopconfirmModule,
|
||||
NzTableModule, NzTabsModule, NzTagModule,
|
||||
} from 'ng-zorro-antd';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AdminTagComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{path: '', component: AdminTagComponent}]),
|
||||
NzCardModule,
|
||||
NzTableModule,
|
||||
NzDividerModule,
|
||||
NzInputModule,
|
||||
FormsModule,
|
||||
NzTabsModule,
|
||||
NzPopconfirmModule,
|
||||
NzButtonModule,
|
||||
NzIconModule,
|
||||
NzTagModule,
|
||||
]
|
||||
})
|
||||
export class AdminTagModule {
|
||||
}
|
||||
32
src/app/view/admin/admin-update/admin-update.component.html
Normal file
32
src/app/view/admin/admin-update/admin-update.component.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<div class="inner-content">
|
||||
<nz-card nzTitle="更新内容管理" nzSize="small">
|
||||
<button nz-button (click)="showModal()" style="margin-bottom: 15px;">新增</button>
|
||||
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
|
||||
[nzPageSize]="pageSize" [nzLoading]="loading"
|
||||
(nzPageIndexChange)="getUpdateInfo()" nzFrontPagination="false">
|
||||
<thead>
|
||||
<th>更新内容</th>
|
||||
<th>更新日期</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of table.data">
|
||||
<td nz-typography nzEllipsis="true" [nzTooltipTitle]="data.info" nzTooltipPlacement="top"
|
||||
nz-tooltip>{{data.info}}</td>
|
||||
<td>{{data.time}}</td>
|
||||
<td>
|
||||
<a (click)="showModal(data)" class="edit-opr">编辑</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a nz-popconfirm nzPopconfirmTitle="确定要删除这个更新吗?" nzOkText="删除" nzCancelText="点错了"
|
||||
(nzOnConfirm)="deleteUpdateInfo(data.id)" class="del-opr">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
</nz-card>
|
||||
</div>
|
||||
|
||||
<nz-modal [(nzVisible)]="modalData.visible" [nzClosable]="true" (nzOnCancel)="modalData.visible = false"
|
||||
(nzOnOk)="confirm()" [nzTitle]="modalData.title">
|
||||
<textarea nz-input [(ngModel)]="modalData.content" [nzAutosize]="{ minRows: 2, maxRows: 8 }"></textarea>
|
||||
</nz-modal>
|
||||
@@ -0,0 +1,3 @@
|
||||
td {
|
||||
max-width: 300px;
|
||||
}
|
||||
95
src/app/view/admin/admin-update/admin-update.component.ts
Normal file
95
src/app/view/admin/admin-update/admin-update.component.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {Observable} from 'rxjs';
|
||||
import {ApiService} from '../../../api/api.service';
|
||||
import {PageList, Response} from '../../../class/HttpReqAndResp';
|
||||
import {UpdateInfo} from '../../../class/UpdateInfo';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-update',
|
||||
templateUrl: './admin-update.component.html',
|
||||
styleUrls: ['./admin-update.component.less']
|
||||
})
|
||||
export class AdminUpdateComponent implements OnInit {
|
||||
|
||||
|
||||
constructor(private apiService: ApiService, private nzMessage: NzMessageService, private title: Title) {
|
||||
}
|
||||
|
||||
pageIndex: number = 1;
|
||||
pageSize: number = 10;
|
||||
|
||||
pageList: PageList<UpdateInfo> = new PageList();
|
||||
|
||||
loading: boolean = true;
|
||||
|
||||
modalData = {
|
||||
visible: false,
|
||||
content: null,
|
||||
id: null,
|
||||
title: null
|
||||
};
|
||||
|
||||
ngOnInit(): void {
|
||||
this.title.setTitle('小海博客 | 更新信息管理')
|
||||
this.getUpdateInfo();
|
||||
}
|
||||
|
||||
getUpdateInfo = () => this.apiService.webUpdatePage(this.pageSize, this.pageIndex).subscribe({
|
||||
next: data => this.pageList = data.result,
|
||||
complete: () => this.loading = false,
|
||||
error: err => this.loading = false
|
||||
})
|
||||
|
||||
|
||||
deleteUpdateInfo(id) {
|
||||
this.loading = true;
|
||||
this.apiService.deleteWebUpdateInfo(id).subscribe({
|
||||
next: data => {
|
||||
this.nzMessage.success('删除成功')
|
||||
this.loading = false;
|
||||
this.getUpdateInfo();
|
||||
},
|
||||
error: err => {
|
||||
this.nzMessage.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
confirm() {
|
||||
this.loading = true;
|
||||
this.modalData.visible = false;
|
||||
let observable: Observable<Response<UpdateInfo>>
|
||||
if (this.modalData.id) {
|
||||
observable = this.apiService.updateWebUpdateInfo(this.modalData.id, this.modalData.content)
|
||||
} else {
|
||||
observable = this.apiService.createWebUpdateInfo(this.modalData.content)
|
||||
}
|
||||
observable.subscribe({
|
||||
next: data => {
|
||||
this.nzMessage.success('操作成功')
|
||||
this.loading = false;
|
||||
this.getUpdateInfo();
|
||||
},
|
||||
error: err => {
|
||||
this.nzMessage.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
console.log(this.modalData);
|
||||
}
|
||||
|
||||
showModal(data?: UpdateInfo) {
|
||||
this.modalData.id = null;
|
||||
this.modalData.title = '新增更新信息';
|
||||
this.modalData.content = null;
|
||||
if (data) {
|
||||
this.modalData.id = data.id;
|
||||
this.modalData.content = data.info;
|
||||
this.modalData.title = '编辑更新信息';
|
||||
}
|
||||
this.modalData.visible = true;
|
||||
}
|
||||
}
|
||||
37
src/app/view/admin/admin-update/admin-update.module.ts
Normal file
37
src/app/view/admin/admin-update/admin-update.module.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AdminUpdateComponent} from './admin-update.component';
|
||||
import {
|
||||
NzButtonModule,
|
||||
NzCardModule,
|
||||
NzDividerModule, NzInputModule, NzModalModule,
|
||||
NzPopconfirmModule,
|
||||
NzTableModule,
|
||||
NzToolTipModule,
|
||||
NzTypographyModule
|
||||
} from 'ng-zorro-antd';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AdminUpdateComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{path: '', component: AdminUpdateComponent}]),
|
||||
NzCardModule,
|
||||
NzTableModule,
|
||||
NzTypographyModule,
|
||||
NzToolTipModule,
|
||||
NzDividerModule,
|
||||
NzPopconfirmModule,
|
||||
NzModalModule,
|
||||
FormsModule,
|
||||
NzButtonModule,
|
||||
NzInputModule
|
||||
]
|
||||
})
|
||||
export class AdminUpdateModule {
|
||||
}
|
||||
117
src/app/view/admin/admin-user/admin-user.component.html
Normal file
117
src/app/view/admin/admin-user/admin-user.component.html
Normal file
@@ -0,0 +1,117 @@
|
||||
<div class="inner-content">
|
||||
<nz-card nzTitle="用户管理" nzSize="small">
|
||||
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
|
||||
[nzPageSize]="pageSize" [nzLoading]="loading"
|
||||
(nzPageIndexChange)="getUser()" nzFrontPagination="false">
|
||||
<thead>
|
||||
<th>邮箱</th>
|
||||
<th>昵称</th>
|
||||
<th>角色</th>
|
||||
<th>邮箱验证状态</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of table.data">
|
||||
<td>{{data.email}}</td>
|
||||
<td>{{data.displayName}}</td>
|
||||
<td>
|
||||
<nz-tag [nzColor]="'blue'" *ngIf="data.role == 'admin'">{{data.role}}</nz-tag>
|
||||
<nz-tag [nzColor]="'purple'" *ngIf="data.role == 'user'">{{data.role}}</nz-tag>
|
||||
</td>
|
||||
<td>
|
||||
<nz-tag [nzColor]="'green'" *ngIf="data.emailStatus">已验证</nz-tag>
|
||||
<nz-tag [nzColor]="'red'" *ngIf="!data.emailStatus">未验证</nz-tag>
|
||||
</td>
|
||||
<td>
|
||||
<a (click)="showModal(true, data)" class="edit-opr">编辑</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a (click)="showModal(false, data)" class="show-opr">查看</a>
|
||||
<nz-divider nzType="vertical"></nz-divider>
|
||||
<a nz-popconfirm nzPopconfirmTitle="确定要删除这个用户吗?" nzOkText="删除" nzCancelText="点错了"
|
||||
(nzOnConfirm)="deleteUser(data.id)" class="del-opr">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
</nz-card>
|
||||
</div>
|
||||
|
||||
<nz-modal [(nzVisible)]="modalData.visible" [nzClosable]="true" [nzTitle]="modalData.title"
|
||||
(nzOnCancel)="modalData.visible = false" (nzOnOk)="modalConfirm()"
|
||||
[nzFooter]="modalData.isEdit?editContentFooter:showContentFooter"
|
||||
[nzContent]="showContent">
|
||||
<ng-template #showContent>
|
||||
<form nz-form [formGroup]="formGroup" (ngSubmit)="modalConfirm()">
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="4" nzRequired>邮箱</nz-form-label>
|
||||
<nz-form-control nzSpan="18">
|
||||
<input type="email" nz-input formControlName="email"
|
||||
[disabled]="!modalData.isEdit">
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="4" nzRequired>昵称</nz-form-label>
|
||||
<nz-form-control nzSpan="18">
|
||||
<input type="text" nz-input formControlName="displayName" [disabled]="!modalData.isEdit">
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="4" nzRequired>角色</nz-form-label>
|
||||
<nz-form-control nzSpan="18">
|
||||
<nz-select formControlName="role" [nzDisabled]="!modalData.isEdit||formGroup.value.id==user.id">
|
||||
<nz-option nzValue="admin" nzLabel="admin"></nz-option>
|
||||
<nz-option nzValue="user" nzLabel="user"></nz-option>
|
||||
</nz-select>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="4" nzRequired>状态</nz-form-label>
|
||||
<nz-form-control nzSpan="18">
|
||||
<nz-radio-group formControlName="emailStatus" [nzDisabled]="!modalData.isEdit">
|
||||
<label nz-radio [nzValue]="true">邮箱已验证</label>
|
||||
<label nz-radio [nzValue]="false">邮箱未验证</label>
|
||||
</nz-radio-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item *ngIf="modalData.isEdit">
|
||||
<nz-form-label nzSpan="4">密码</nz-form-label>
|
||||
<nz-form-control nzSpan="18">
|
||||
<a *ngIf="!modalData.resetPwd" (click)="modalData.resetPwd = true">
|
||||
重设密码<i nz-icon nzType="edit" nzTheme="twotone" style="margin-left: 10px;"></i>
|
||||
</a>
|
||||
<nz-input-group *ngIf="modalData.resetPwd" [nzSuffix]="cancelBtn" nzSize="small">
|
||||
<input type="password" nz-input formControlName="pwd" autocomplete="new-password"
|
||||
[disabled]="!modalData.isEdit">
|
||||
<ng-template #cancelBtn>
|
||||
<button nz-button (click)="modalData.resetPwd = false" nzSize="small" nzType="link">取消
|
||||
</button>
|
||||
</ng-template>
|
||||
</nz-input-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="4">描述</nz-form-label>
|
||||
<nz-form-control nzSpan="18">
|
||||
<textarea nz-input [nzAutosize]="{ minRows: 2, maxRows: 4 }" formControlName="desc"
|
||||
[disabled]="!modalData.isEdit"></textarea>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</form>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #showContentFooter>
|
||||
<button nz-button (click)="modalData.visible = false">关闭</button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #editContentFooter>
|
||||
<button nz-button (click)="modalData.visible = false">取消</button>
|
||||
<button nz-button (click)="modalConfirm()" nzType="primary">提交</button>
|
||||
</ng-template>
|
||||
|
||||
</nz-modal>
|
||||
93
src/app/view/admin/admin-user/admin-user.component.ts
Normal file
93
src/app/view/admin/admin-user/admin-user.component.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {FormControl, FormGroup} from '@angular/forms';
|
||||
import {PageList} from '../../../class/HttpReqAndResp';
|
||||
import {ApiService} from '../../../api/api.service';
|
||||
import {User} from '../../../class/User';
|
||||
import {GlobalUserService} from '../../../services/global-user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-user',
|
||||
templateUrl: './admin-user.component.html',
|
||||
styleUrls: ['./admin-user.component.less']
|
||||
})
|
||||
export class AdminUserComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private title: Title, private messageService: NzMessageService,
|
||||
private userService: GlobalUserService) {
|
||||
this.formGroup = new FormGroup({
|
||||
id: new FormControl(null),
|
||||
email: new FormControl(''),
|
||||
displayName: new FormControl(''),
|
||||
emailStatus: new FormControl(null),
|
||||
desc: new FormControl(null),
|
||||
role: new FormControl(null),
|
||||
pwd: new FormControl(''),
|
||||
});
|
||||
this.userService.watchUserInfo({
|
||||
next: data => this.user = data.result,
|
||||
error: null,
|
||||
complete: null
|
||||
})
|
||||
}
|
||||
|
||||
pageIndex: number = 1;
|
||||
pageSize: number = 10;
|
||||
|
||||
pageList: PageList<User> = new PageList<User>();
|
||||
user: User;
|
||||
loading: boolean = true;
|
||||
modalData = {
|
||||
visible: false,
|
||||
title: null,
|
||||
isEdit: false,
|
||||
resetPwd: false
|
||||
}
|
||||
formGroup: FormGroup;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.title.setTitle('小海博客 | 用户管理')
|
||||
this.getUser();
|
||||
}
|
||||
|
||||
getUser = () => this.apiService.adminUsers(this.pageSize, this.pageIndex).subscribe({
|
||||
next: data => this.pageList = data.result,
|
||||
complete: () => this.loading = false,
|
||||
error: err => this.loading = false
|
||||
})
|
||||
|
||||
deleteUser(id) {
|
||||
this.loading = true;
|
||||
this.apiService.deleteUser(id).subscribe({
|
||||
next: data => {
|
||||
this.messageService.success('删除成功')
|
||||
this.loading = false;
|
||||
this.getUser();
|
||||
},
|
||||
error: err => {
|
||||
this.messageService.error(err.msg)
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
showModal(isEdit: boolean, data: User) {
|
||||
this.modalData.visible = true;
|
||||
this.modalData.isEdit = isEdit;
|
||||
this.modalData.title = isEdit ? '编辑用户' : '查看用户'
|
||||
this.formGroup.reset();
|
||||
this.formGroup.patchValue(data);
|
||||
}
|
||||
|
||||
modalConfirm() {
|
||||
this.modalData.visible = false
|
||||
this.apiService.adminUpdateUser(this.formGroup.value).subscribe({
|
||||
next: data => {
|
||||
this.getUser();
|
||||
this.messageService.success('修改用户信息成功');
|
||||
this.userService.refreshUserInfo();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
41
src/app/view/admin/admin-user/admin-user.module.ts
Normal file
41
src/app/view/admin/admin-user/admin-user.module.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AdminUserComponent} from './admin-user.component';
|
||||
import {
|
||||
NzButtonModule,
|
||||
NzCardModule,
|
||||
NzDividerModule, NzFormModule, NzIconModule, NzInputModule,
|
||||
NzModalModule,
|
||||
NzPopconfirmModule, NzRadioModule, NzSelectModule,
|
||||
NzTableModule,
|
||||
NzTagModule
|
||||
} from 'ng-zorro-antd';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AdminUserComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{path: '', component: AdminUserComponent}]),
|
||||
NzCardModule,
|
||||
NzTableModule,
|
||||
NzPopconfirmModule,
|
||||
NzDividerModule,
|
||||
NzTagModule,
|
||||
NzModalModule,
|
||||
NzButtonModule,
|
||||
NzFormModule,
|
||||
ReactiveFormsModule,
|
||||
NzInputModule,
|
||||
NzSelectModule,
|
||||
NzRadioModule,
|
||||
NzIconModule
|
||||
|
||||
]
|
||||
})
|
||||
export class AdminUserModule {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<div class="inner-content">
|
||||
<nz-card nzTitle="访客信息管理" nzSize="small">
|
||||
<nz-table #table [nzData]="pageList.list" [nzTotal]="pageList.total" [(nzPageIndex)]="pageIndex"
|
||||
[nzPageSize]="pageSize" [nzLoading]="loading"
|
||||
(nzPageIndexChange)="getVisitors()" nzFrontPagination="false">
|
||||
<thead>
|
||||
<th>ip地址</th>
|
||||
<th>访问日期</th>
|
||||
<th>浏览器类型</th>
|
||||
<th>浏览器版本</th>
|
||||
<th>系统</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of table.data">
|
||||
<td>{{data.ip}}</td>
|
||||
<td>{{data.date}}</td>
|
||||
<td>{{data.browserName}}</td>
|
||||
<td>{{data.browserVersion}}</td>
|
||||
<td>{{data.osname}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
</nz-card>
|
||||
</div>
|
||||
34
src/app/view/admin/admin-visitor/admin-visitor.component.ts
Normal file
34
src/app/view/admin/admin-visitor/admin-visitor.component.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {ApiService} from '../../../api/api.service';
|
||||
import {PageList} from '../../../class/HttpReqAndResp';
|
||||
import {Visitor} from '../../../class/Visitor';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-visitor',
|
||||
templateUrl: './admin-visitor.component.html',
|
||||
styleUrls: ['./admin-visitor.component.less']
|
||||
})
|
||||
export class AdminVisitorComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private title: Title) {
|
||||
}
|
||||
|
||||
pageIndex: number = 1;
|
||||
pageSize: number = 10;
|
||||
|
||||
pageList: PageList<Visitor> = new PageList<Visitor>();
|
||||
|
||||
loading: boolean = true;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.title.setTitle('小海博客 | 访客信息管理')
|
||||
this.getVisitors();
|
||||
}
|
||||
|
||||
getVisitors = () => this.apiService.adminVisitors(false, this.pageSize, this.pageIndex).subscribe({
|
||||
next: data => this.pageList = data.result,
|
||||
complete: () => this.loading = false,
|
||||
error: err => this.loading = false
|
||||
})
|
||||
}
|
||||
22
src/app/view/admin/admin-visitor/admin-visitor.module.ts
Normal file
22
src/app/view/admin/admin-visitor/admin-visitor.module.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AdminVisitorComponent} from './admin-visitor.component';
|
||||
import {NzButtonModule, NzCardModule, NzDividerModule, NzTableModule} from 'ng-zorro-antd';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AdminVisitorComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{path: '', component: AdminVisitorComponent}]),
|
||||
NzCardModule,
|
||||
NzTableModule,
|
||||
NzButtonModule,
|
||||
NzDividerModule
|
||||
]
|
||||
})
|
||||
export class AdminVisitorModule {
|
||||
}
|
||||
159
src/app/view/admin/admin.component.html
Normal file
159
src/app/view/admin/admin.component.html
Normal file
@@ -0,0 +1,159 @@
|
||||
<c-admin-header (infoClicked)="showInfoDrawer()"></c-admin-header>
|
||||
<nz-layout class="layout">
|
||||
<nz-sider nzCollapsible
|
||||
[(nzCollapsed)]="isCollapsed"
|
||||
[nzBreakpoint]="'lg'"
|
||||
[nzCollapsedWidth]="0"
|
||||
[nzZeroTrigger]="zeroTrigger"
|
||||
nzTheme="light"
|
||||
*ngIf="user">
|
||||
<ul nz-menu nzTheme="light" nzMode="inline" [nzInlineCollapsed]="isCollapsed">
|
||||
<li nz-menu-item routerLink="/admin">
|
||||
<i nz-icon nzType="dashboard" nzTheme="outline"></i>
|
||||
<span>后台首页</span>
|
||||
</li>
|
||||
|
||||
<li nz-submenu nzTitle="文章管理" nzIcon="file" *ngIf="user.role=='admin'">
|
||||
<ul>
|
||||
<li nz-menu-item>
|
||||
<a routerLink="/write">
|
||||
<i nz-icon nzType="form" nzTheme="outline"></i>
|
||||
<span>新增文章</span>
|
||||
</a>
|
||||
</li>
|
||||
<li nz-menu-item routerLink="/admin/article">
|
||||
<i nz-icon nzType="ordered-list" nzTheme="outline"></i>
|
||||
<span>文章列表</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li nz-menu-item routerLink="/admin/comment">
|
||||
<i nz-icon nzType="message" nzTheme="outline"></i>
|
||||
<span>评论管理</span>
|
||||
</li>
|
||||
|
||||
<li nz-menu-item routerLink="/admin/tag" *ngIf="user.role=='admin'">
|
||||
<i nz-icon nzType="tags" nzTheme="outline"></i>
|
||||
<span>标签管理</span>
|
||||
</li>
|
||||
|
||||
<!-- <li nz-menu-item routerLink="/admin/category" *ngIf="user.role=='admin'">-->
|
||||
<!-- <i nz-icon nzType="appstore" nzTheme="outline"></i>-->
|
||||
<!-- <span>分类管理</span>-->
|
||||
<!-- </li>-->
|
||||
|
||||
<li nz-menu-item routerLink="/admin/user" *ngIf="user.role=='admin'">
|
||||
<i nz-icon nzType="user" nzTheme="outline"></i>
|
||||
<span>用户管理</span>
|
||||
</li>
|
||||
|
||||
<li nz-menu-item routerLink="/admin/link" *ngIf="user.role=='admin'">
|
||||
<i nz-icon nzType="link" nzTheme="outline"></i>
|
||||
<span>友链管理</span>
|
||||
</li>
|
||||
|
||||
<li nz-menu-item routerLink="/admin/visitor" *ngIf="user.role=='admin'">
|
||||
<i nz-icon nzType="chrome" nzTheme="outline"></i>
|
||||
<span>访问管理</span>
|
||||
</li>
|
||||
|
||||
<li nz-menu-item routerLink="/admin/update" *ngIf="user.role=='admin'">
|
||||
<i nz-icon nzType="arrow-up" nzTheme="outline"></i>
|
||||
<span>更新管理</span>
|
||||
</li>
|
||||
|
||||
<li nz-menu-item (click)="infoDrawerVisible = true">
|
||||
<i nz-icon nzType="idcard" nzTheme="outline"></i>
|
||||
<span>查看信息</span>
|
||||
</li>
|
||||
|
||||
<!--TODO : do something here ..... -->
|
||||
<nz-card class="myCard" *ngIf="!isCollapsed&&user.role=='admin'">
|
||||
<p>别管别人言语</p>
|
||||
<p>做最好的自己</p>
|
||||
</nz-card>
|
||||
<nz-card class="myCard" *ngIf="!isCollapsed&&user.role=='user'">
|
||||
<p>欢迎来访小海博客</p>
|
||||
</nz-card>
|
||||
|
||||
</ul>
|
||||
</nz-sider>
|
||||
<nz-layout>
|
||||
<nz-content>
|
||||
<div class="inner-content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</nz-content>
|
||||
<nz-footer>© <a href="https://www.celess.cn">小海博客</a> - Design by 小海</nz-footer>
|
||||
</nz-layout>
|
||||
</nz-layout>
|
||||
<nz-drawer [nzClosable]="false" [nzVisible]="infoDrawerVisible" nzPlacement="right"
|
||||
[nzTitle]="sayHelloTemp" (nzOnClose)="infoDrawerVisible = false">
|
||||
<p>您最近一次登录是在:</p>
|
||||
<p>{{user.recentlyLandedDate}}</p>
|
||||
<br><br>
|
||||
<nz-descriptions [nzColumn]="1">
|
||||
<nz-descriptions-item nzTitle="邮箱">
|
||||
<!-- 抽屉的宽度是256px -->
|
||||
<div style="width: 256px">
|
||||
<i nz-icon nzType="mail" class="icon" nzTheme="twotone"></i>
|
||||
<span>{{user.email}}</span>
|
||||
<i nz-icon nzType="edit" class="edit-icon" nzTheme="twotone" (click)="showEditInfoModal()"></i>
|
||||
</div>
|
||||
</nz-descriptions-item>
|
||||
<nz-descriptions-item nzTitle="昵称">
|
||||
<div style="width: 256px">
|
||||
<i nz-icon nzType="crown" class="icon" nzTheme="twotone"></i>
|
||||
<span>{{user.displayName}}</span>
|
||||
<i nz-icon nzType="edit" class="edit-icon" nzTheme="twotone" (click)="showEditInfoModal()"></i>
|
||||
</div>
|
||||
</nz-descriptions-item>
|
||||
<nz-descriptions-item nzTitle="描述" *ngIf="user.desc">
|
||||
<div style="width: 256px">
|
||||
<i nz-icon nzType="info-circle" class="icon" nzTheme="twotone"></i>
|
||||
<span>{{user.desc}}</span>
|
||||
<i nz-icon nzType="edit" class="edit-icon" nzTheme="twotone" (click)="showEditInfoModal()"></i>
|
||||
</div>
|
||||
</nz-descriptions-item>
|
||||
</nz-descriptions>
|
||||
|
||||
|
||||
<div nz-row style="text-align: center">
|
||||
<a (click)="logout()" style="width: 100%">注销</a>
|
||||
</div>
|
||||
<!-- TODO: 展示更多信息 -->
|
||||
</nz-drawer>
|
||||
|
||||
<nz-modal [(nzVisible)]="editInfoModalVisible" (nzOnOk)="modalConfirm()" (nzOnCancel)="editInfoModalVisible=false"
|
||||
nzTitle="编辑信息">
|
||||
<form nz-form [formGroup]="editInfoFormGroup" (ngSubmit)="modalConfirm()">
|
||||
<nz-form-item>
|
||||
<nz-form-label>邮箱</nz-form-label>
|
||||
<nz-form-control>
|
||||
<input nz-input disabled formControlName="email">
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label>昵称</nz-form-label>
|
||||
<nz-form-control>
|
||||
<input nz-input formControlName="displayName" placeholder="默认为你的邮箱地址">
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label>描述</nz-form-label>
|
||||
<nz-form-control>
|
||||
<input nz-input formControlName="desc" placeholder="请输入自我介绍">
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</form>
|
||||
</nz-modal>
|
||||
|
||||
<ng-template #zeroTrigger>
|
||||
<i nz-icon nzType="menu-fold" nzTheme=outline></i>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #sayHelloTemp>
|
||||
<span>{{sayHelloContent}}</span>
|
||||
<i nz-icon [nzType]="'smile'" [nzTheme]="'twotone'" nzTwotoneColor="#52c41a" style="margin-left: 5px"></i>
|
||||
</ng-template>
|
||||
48
src/app/view/admin/admin.component.less
Normal file
48
src/app/view/admin/admin.component.less
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
.myCard {
|
||||
width: 180px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
nz-layout, nz-sider {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.layout {
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nz-content {
|
||||
margin-left: 30px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
min-height: 560px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.edit-icon{
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
nz-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
nz-content {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
92
src/app/view/admin/admin.component.ts
Normal file
92
src/app/view/admin/admin.component.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {FormControl, FormGroup} from '@angular/forms';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {Router} from '@angular/router';
|
||||
import {GlobalUserService} from '../../services/global-user.service';
|
||||
import {User} from '../../class/User';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin',
|
||||
templateUrl: './admin.component.html',
|
||||
styleUrls: ['./admin.component.less']
|
||||
})
|
||||
export class AdminComponent implements OnInit {
|
||||
|
||||
constructor(public gUserService: GlobalUserService, private apiService: ApiService, private messageService: NzMessageService,
|
||||
private router: Router) {
|
||||
this.gUserService.watchUserInfo({
|
||||
complete: () => null,
|
||||
error: (err) => null,
|
||||
next: data => {
|
||||
console.log('更新user')
|
||||
this.user = data.result
|
||||
if (data.result) this.initHelloWords()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
user: User;
|
||||
isCollapsed: boolean = false;
|
||||
infoDrawerVisible: boolean = false;
|
||||
sayHelloContent: string;
|
||||
editInfoModalVisible: boolean = false;
|
||||
editInfoFormGroup: FormGroup;
|
||||
|
||||
showInfoDrawer = () => this.infoDrawerVisible = !this.infoDrawerVisible;
|
||||
|
||||
logout() {
|
||||
this.gUserService.logout();
|
||||
this.router.navigateByUrl('/')
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.editInfoFormGroup = new FormGroup({
|
||||
desc: new FormControl(),
|
||||
displayName: new FormControl(),
|
||||
email: new FormControl({value: null, disabled: true})
|
||||
});
|
||||
}
|
||||
|
||||
private initHelloWords() {
|
||||
const hours = new Date().getHours();
|
||||
if (hours < 6) {
|
||||
this.sayHelloContent = `夜深了,注意早点休息哦!${this.user.displayName}`
|
||||
} else if (hours < 10) {
|
||||
this.sayHelloContent = `早上好呀!${this.user.displayName}`
|
||||
} else if (hours < 14) {
|
||||
this.sayHelloContent = `中午好呀!${this.user.displayName}`
|
||||
} else if (hours < 19) {
|
||||
this.sayHelloContent = `下午好呀!${this.user.displayName}`
|
||||
} else if (hours < 22) {
|
||||
this.sayHelloContent = `晚上好呀!${this.user.displayName}`
|
||||
} else {
|
||||
this.sayHelloContent = `时间不早了,注意休息哦!${this.user.displayName}`
|
||||
}
|
||||
}
|
||||
|
||||
showEditInfoModal() {
|
||||
this.editInfoModalVisible = true;
|
||||
this.infoDrawerVisible = false;
|
||||
this.editInfoFormGroup.patchValue(this.user);
|
||||
}
|
||||
|
||||
modalConfirm() {
|
||||
const desc = this.editInfoFormGroup.value.desc;
|
||||
const displayName = this.editInfoFormGroup.value.displayName;
|
||||
this.apiService.updateUserInfo(desc, displayName).subscribe({
|
||||
next: data => {
|
||||
this.messageService.success('修改信息成功')
|
||||
this.gUserService.refreshUserInfo();
|
||||
},
|
||||
error: err => {
|
||||
this.messageService.error(err.msg);
|
||||
this.gUserService.refreshUserInfo();
|
||||
},
|
||||
complete: null
|
||||
});
|
||||
this.editInfoModalVisible = false;
|
||||
}
|
||||
|
||||
}
|
||||
25
src/app/view/admin/admin.module.ts
Normal file
25
src/app/view/admin/admin.module.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AdminRoutingModule} from './admin-routing.module';
|
||||
import {AdminComponent} from './admin.component';
|
||||
import {NgZorroAntdModule} from 'ng-zorro-antd';
|
||||
import {NzSpaceModule} from 'ng-zorro-antd/space';
|
||||
import {AdminHeaderComponent} from '../../components/admin-header/admin-header.component';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AdminHeaderComponent,
|
||||
AdminComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AdminRoutingModule,
|
||||
NgZorroAntdModule,
|
||||
NzSpaceModule,
|
||||
ReactiveFormsModule
|
||||
]
|
||||
})
|
||||
export class AdminModule {
|
||||
}
|
||||
70
src/app/view/admin/auth.guard.ts
Normal file
70
src/app/view/admin/auth.guard.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router} from '@angular/router';
|
||||
import {Observable, Observer} from 'rxjs';
|
||||
import {User} from '../../class/User';
|
||||
import {GlobalUserService} from '../../services/global-user.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
constructor(private userService: GlobalUserService, private router: Router) {
|
||||
// this.userService.refreshUserInfo();
|
||||
}
|
||||
|
||||
userInfo: User;
|
||||
visitCount: number = 0; // 记录一共走过几次canActivate
|
||||
private path: string;
|
||||
private readonly loginPath: string = '/user/login';
|
||||
private observable: Observable<boolean>;
|
||||
|
||||
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
this.path = state.url.indexOf('?') > 0 ? state.url.substr(0, state.url.indexOf('?')) : state.url;
|
||||
this.visitCount++;
|
||||
this.observable = new Observable<boolean>(observer => {
|
||||
if (!this.userInfo) {
|
||||
this.watchUserInfo(observer);
|
||||
} else {
|
||||
this.checkPath(observer);
|
||||
}
|
||||
});
|
||||
return this.observable;
|
||||
}
|
||||
|
||||
watchUserInfo(observer: Observer<boolean>) {
|
||||
this.userService.watchUserInfo({
|
||||
complete: null,
|
||||
error: (err) => {
|
||||
// 请求重复
|
||||
if (err.code !== -1) {
|
||||
observer.next(false);
|
||||
this.router.navigateByUrl(this.loginPath);
|
||||
}
|
||||
},
|
||||
next: data => {
|
||||
this.userInfo = data.result;
|
||||
this.checkPath(observer);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
checkPath(observer: Observer<boolean>) {
|
||||
switch (this.path) {
|
||||
case '/admin/article':
|
||||
case '/admin/link':
|
||||
case '/admin/tag':
|
||||
case '/admin/update':
|
||||
case '/admin/user':
|
||||
case '/admin/visitor':
|
||||
if (this.userInfo.role !== 'admin') {
|
||||
observer.next(false)
|
||||
if (this.visitCount === 1) this.router.navigateByUrl('/admin')
|
||||
return;
|
||||
}
|
||||
}
|
||||
observer.next(true);
|
||||
}
|
||||
|
||||
}
|
||||
18
src/app/view/article/article-routing.module.ts
Normal file
18
src/app/view/article/article-routing.module.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {ArticleComponent} from './article.component';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '**', component: ArticleComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
|
||||
export class ArticleRoutingModule {
|
||||
}
|
||||
109
src/app/view/article/article.component.html
Normal file
109
src/app/view/article/article.component.html
Normal file
@@ -0,0 +1,109 @@
|
||||
<div id="main">
|
||||
|
||||
<div id="article-content"></div>
|
||||
<ng-template [ngIf]="article">
|
||||
<span id="over">over</span>
|
||||
|
||||
<!-- 文章版权 -->
|
||||
<div id="copyright">
|
||||
<p>本文作者:{{article.authorName}} </p>
|
||||
<p>{{article.original ? "本文" : "原文"}}链接:{{article.original ? copyRightUrl : article.url}}</p>
|
||||
<p>版权声明:转载请注明出处</p>
|
||||
</div>
|
||||
|
||||
<nz-divider></nz-divider>
|
||||
<div class="article-tag" id="tag">
|
||||
<!-- TODO -->
|
||||
<span *ngFor="let item of (article.tags||'')" class="tag">
|
||||
<i nz-icon nzType="tag" nzTheme="fill"></i>
|
||||
<a class="tag" [routerLink]="['/tags']" [queryParams]="{name:item}">{{item}}</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="article-bAnda">
|
||||
<a (click)="toArticle(article.nextArticleId)" [class.disabled]="article.nextArticleId==-1">
|
||||
<i nz-icon nzType="left" nzTheme="outline"></i> {{article.nextArticleTitle}}
|
||||
</a>
|
||||
<a (click)="toArticle(article.preArticleId)" [class.disabled]="article.preArticleId==-1"
|
||||
style="float: right" id="pre">
|
||||
{{article.preArticleTitle}} <i nz-icon nzType="right" nzTheme="outline"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- TODO:::评论列表 -->
|
||||
</ng-template>
|
||||
|
||||
<nz-comment id="f-comment">
|
||||
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="avatarImgUrl"></nz-avatar>
|
||||
<nz-comment-content>
|
||||
<nz-form-item>
|
||||
<textarea [(ngModel)]="comment.content" nz-input rows="4" [disabled]="!user"
|
||||
[nzAutosize]="{ minRows: 2, maxRows: user?6:2 }"></textarea>
|
||||
<span *ngIf="!user">请先 <a routerLink="/user/login"
|
||||
[queryParams]="{url:'/article/'+articleId}">登录</a></span>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button nzType="primary" [nzLoading]="!user&&submitting"
|
||||
[disabled]="!comment.content"
|
||||
(click)="submitComment(true)">评论
|
||||
</button>
|
||||
</nz-form-item>
|
||||
</nz-comment-content>
|
||||
</nz-comment>
|
||||
|
||||
<div class="tab">评论</div>
|
||||
<div class="tab-bottom"></div>
|
||||
<div *ngIf="commentPage&&!commentPage.list.length" class="no-comment-tip">暂无评论,赶快去抢个沙发吧</div>
|
||||
<ng-template [ngIf]="commentPage">
|
||||
|
||||
<nz-list [nzLoading]="!commentPage">
|
||||
<nz-list-item *ngFor="let comment of commentPage.list">
|
||||
<nz-comment [nzAuthor]="comment.authorName" [nzDatetime]="comment.date" style="width: 100%">
|
||||
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="comment.authorAvatarImgUrl"></nz-avatar>
|
||||
<nz-comment-content>
|
||||
<p style="font-size: larger">{{ comment.content }}</p>
|
||||
</nz-comment-content>
|
||||
<nz-comment-action>
|
||||
<i nz-icon nzType="message" nzTheme="outline"></i>
|
||||
<button nz-button nzType="link" (click)="resp(comment.id,comment.authorName)">回复</button>
|
||||
</nz-comment-action>
|
||||
|
||||
<nz-list-item *ngFor="let com of comment.respComment">
|
||||
<nz-comment [nzAuthor]="com.authorName" [nzDatetime]="com.date">
|
||||
<nz-avatar nz-comment-avatar nzIcon="user" [nzSrc]="com.authorAvatarImgUrl"></nz-avatar>
|
||||
<nz-comment-content>
|
||||
<p style="font-size: larger">{{ com.content }}</p>
|
||||
</nz-comment-content>
|
||||
<!--<nz-comment-action>
|
||||
<i nz-icon nzType="message" nzTheme="outline"></i>
|
||||
<button nz-button nzType="link" (click)="resp(comment.id,com.authorName)">回复</button>
|
||||
</nz-comment-action>-->
|
||||
</nz-comment>
|
||||
</nz-list-item>
|
||||
|
||||
<nz-form-item *ngIf="pid==comment.id">
|
||||
<nz-input-group [nzAddOnBefore]="respName">
|
||||
<textarea nz-input [(ngModel)]="content" placeholder="写出你的想法"
|
||||
[nzAutosize]="{ minRows: 2, maxRows: 6 }" [disabled]="!user">
|
||||
</textarea>
|
||||
<ng-template #respName>
|
||||
<span>@{{name}}</span>
|
||||
</ng-template>
|
||||
</nz-input-group>
|
||||
<div *ngIf="!user">请先 <a routerLink="/user/login"
|
||||
[queryParams]="{url:'/article/'+articleId}">登录</a></div>
|
||||
<button nz-button (click)="pid=null" style="margin-top: 10px;">取消</button>
|
||||
<button nz-button nzType="primary" (click)="submitComment(false)"
|
||||
style="margin-left: 30px;margin-top: 10px;" [nzLoading]="!user&&submitting">回复
|
||||
</button>
|
||||
|
||||
</nz-form-item>
|
||||
</nz-comment>
|
||||
|
||||
</nz-list-item>
|
||||
</nz-list>
|
||||
</ng-template>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
113
src/app/view/article/article.component.less
Normal file
113
src/app/view/article/article.component.less
Normal file
@@ -0,0 +1,113 @@
|
||||
|
||||
nz-sider {
|
||||
width: 200px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
nz-layout {
|
||||
background: white;
|
||||
}
|
||||
|
||||
nz-content {
|
||||
margin: 24px 15%;
|
||||
width: 70%;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#article-content {
|
||||
img {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
#over {
|
||||
display: block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
line-height: 80px;
|
||||
margin: 15px auto;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 2em;
|
||||
background: #3bb4f2;
|
||||
color: white
|
||||
}
|
||||
|
||||
#copyright {
|
||||
width: 90%;
|
||||
margin: 15px 5%;
|
||||
border-left: 5px solid #FF1700;
|
||||
padding: 10px;
|
||||
background: #ececec
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: #666666;
|
||||
margin-right: 10px;
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.article-bAnda {
|
||||
margin-top: 10px;
|
||||
|
||||
a {
|
||||
color: black;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #00a8c6;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
cursor: none;
|
||||
color: gray;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid #f50;
|
||||
border-bottom: 1px solid #ffffff;
|
||||
padding: 8px 10px;
|
||||
width: 50px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tab-bottom {
|
||||
border-top: 1px solid #f50;
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.no-comment-tip {
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#main {
|
||||
background: white;
|
||||
padding: 0 15%
|
||||
}
|
||||
|
||||
@media screen and (max-width: 940px) {
|
||||
#main {
|
||||
padding: 0 2%;
|
||||
}
|
||||
|
||||
.article-bAnda {
|
||||
height: 70px;
|
||||
max-height: 110px;
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
#f-comment {
|
||||
margin: 30px 3%
|
||||
}
|
||||
}
|
||||
25
src/app/view/article/article.component.spec.ts
Normal file
25
src/app/view/article/article.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ArticleComponent } from './article.component';
|
||||
|
||||
describe('ArticleComponent', () => {
|
||||
let component: ArticleComponent;
|
||||
let fixture: ComponentFixture<ArticleComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ArticleComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ArticleComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
112
src/app/view/article/article.component.ts
Normal file
112
src/app/view/article/article.component.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {Article} from '../../class/Article';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {User} from '../../class/User';
|
||||
import {CommentReq} from '../../class/Comment';
|
||||
import {PageList} from '../../class/HttpReqAndResp';
|
||||
import {Comment} from '../../class/Comment';
|
||||
import {GlobalUserService} from '../../services/global-user.service';
|
||||
|
||||
declare var editormd;
|
||||
declare var $;
|
||||
|
||||
@Component({
|
||||
selector: 'view-article',
|
||||
templateUrl: './article.component.html',
|
||||
styleUrls: ['./article.component.less']
|
||||
})
|
||||
export class ArticleComponent implements OnInit {
|
||||
|
||||
constructor(private activatedRoute: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
private userService: GlobalUserService,
|
||||
private titleService: Title,
|
||||
private router: Router) {
|
||||
this.articleId = +activatedRoute.snapshot.paramMap.get('id');
|
||||
}
|
||||
|
||||
articleId: number;
|
||||
article: Article;
|
||||
copyRightUrl: string;
|
||||
user: User;
|
||||
submitting: boolean = false;
|
||||
|
||||
comment: CommentReq;
|
||||
// 作为输入框@ 的提示
|
||||
name: string;
|
||||
commentPage: PageList<Comment>;
|
||||
avatarImgUrl: string;
|
||||
pid: number;
|
||||
content: string;
|
||||
|
||||
ngOnInit() {
|
||||
this.toArticle(this.articleId);
|
||||
this.userService.watchUserInfo({
|
||||
complete: () => null,
|
||||
error: (err) => null,
|
||||
next: data => {
|
||||
this.user = data.result;
|
||||
if (data.result) this.avatarImgUrl = data.result.avatarImgUrl;
|
||||
}
|
||||
});
|
||||
this.comment = new CommentReq(true);
|
||||
}
|
||||
|
||||
parseMd(md: string) {
|
||||
editormd.markdownToHTML('article-content', {
|
||||
markdown: this.article.mdContent,
|
||||
htmlDecode: 'style,script,iframe', // you can filter tags decode
|
||||
toc: false,
|
||||
tocm: false, // Using [TOCM]
|
||||
// tocContainer: '#article-slider', // 自定义 ToC 容器层
|
||||
// tocDropdown: true,
|
||||
emoji: true,
|
||||
taskList: true,
|
||||
flowChart: true, // 默认不解析
|
||||
});
|
||||
}
|
||||
|
||||
toArticle(id: number) {
|
||||
this.commentPage = null;
|
||||
this.router.navigateByUrl('/article/' + id);
|
||||
this.apiService.getArticle(id).subscribe({
|
||||
next: data => {
|
||||
this.apiService.comments(id).subscribe(commData => {
|
||||
this.commentPage = commData.result;
|
||||
});
|
||||
document.getElementById('article-content').innerHTML = '';
|
||||
this.article = data.result;
|
||||
this.parseMd(data.result.mdContent);
|
||||
this.titleService.setTitle('小海博客 | ' + data.result.title);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
this.copyRightUrl = location.href;
|
||||
}
|
||||
|
||||
// true ==> 一级评论 false ==>二级评论
|
||||
submitComment(bool: boolean) {
|
||||
this.submitting = true;
|
||||
this.comment.articleID = this.articleId;
|
||||
if (!bool) {
|
||||
this.comment.content = this.content;
|
||||
this.comment.pid = this.pid;
|
||||
}
|
||||
this.apiService.createComment(this.comment).subscribe(data => {
|
||||
this.commentPage.list.push(data.result);
|
||||
this.comment.content = null;
|
||||
this.comment.pid = null;
|
||||
this.submitting = false;
|
||||
}, error => {
|
||||
this.submitting = false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
resp(id: number, name: string) {
|
||||
this.pid = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
42
src/app/view/article/article.module.ts
Normal file
42
src/app/view/article/article.module.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {ArticleRoutingModule} from './article-routing.module';
|
||||
import {ArticleComponent} from './article.component';
|
||||
import {
|
||||
NzAffixModule,
|
||||
NzAnchorModule, NzAvatarModule,
|
||||
NzButtonModule,
|
||||
NzCommentModule,
|
||||
NzDividerModule, NzFormModule,
|
||||
NzGridModule,
|
||||
NzIconModule, NzInputModule,
|
||||
NzLayoutModule, NzListModule, NzTabsModule
|
||||
} from 'ng-zorro-antd';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ArticleComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ArticleRoutingModule,
|
||||
NzGridModule,
|
||||
NzAnchorModule,
|
||||
NzAffixModule,
|
||||
NzButtonModule,
|
||||
NzIconModule,
|
||||
NzLayoutModule,
|
||||
NzDividerModule,
|
||||
NzCommentModule,
|
||||
NzFormModule,
|
||||
FormsModule,
|
||||
NzAvatarModule,
|
||||
NzInputModule,
|
||||
NzListModule,
|
||||
NzTabsModule
|
||||
]
|
||||
})
|
||||
export class ArticleModule {
|
||||
}
|
||||
18
src/app/view/category/category-routing.module.ts
Normal file
18
src/app/view/category/category-routing.module.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {CategoryComponent} from './category.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: ':category', component: CategoryComponent},
|
||||
{path: '**', component: CategoryComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
|
||||
export class CategoryRoutingModule {
|
||||
}
|
||||
28
src/app/view/category/category.component.html
Normal file
28
src/app/view/category/category.component.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<div id="main">
|
||||
|
||||
|
||||
<div id="category">
|
||||
<ul *ngIf="categoryList.length">
|
||||
<c-tag-tag *ngFor="let category of categoryList"
|
||||
[tag]="{name:category.name,size:category.articles.length}"
|
||||
(tagClick)="changeCategory(category)">
|
||||
|
||||
</c-tag-tag>
|
||||
</ul>
|
||||
<h2 *ngIf="!categoryList.length">暂时没有分类</h2>
|
||||
</div>
|
||||
|
||||
<span id="tip">当前分类为 :
|
||||
<span style="font-weight: bolder;">{{name}}</span>
|
||||
</span>
|
||||
|
||||
<ul id="detail" *ngIf="articleList&&articleList.list.length">
|
||||
<c-article-detail-card *ngFor="let article of articleList.list"
|
||||
[data]="article" [showMediaArea]="false" [showTagArea]="false">
|
||||
</c-article-detail-card>
|
||||
</ul>
|
||||
|
||||
<div *ngIf="!articleList||!articleList.list.length">
|
||||
<h2 style="text-align: center">该分类暂无文章</h2>
|
||||
</div>
|
||||
</div>
|
||||
29
src/app/view/category/category.component.less
Normal file
29
src/app/view/category/category.component.less
Normal file
@@ -0,0 +1,29 @@
|
||||
#main {
|
||||
width: 75%;
|
||||
margin: 30px auto;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background: #ffffff;
|
||||
|
||||
#tip {
|
||||
margin-left: 40px;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
#category {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#detail {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 940px) {
|
||||
#main {
|
||||
width: 100%;
|
||||
}
|
||||
#detail{
|
||||
margin: 10px 40px 10px 0;
|
||||
}
|
||||
}
|
||||
25
src/app/view/category/category.component.spec.ts
Normal file
25
src/app/view/category/category.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CategoryComponent } from './category.component';
|
||||
|
||||
describe('CategoryComponent', () => {
|
||||
let component: CategoryComponent;
|
||||
let fixture: ComponentFixture<CategoryComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CategoryComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CategoryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
71
src/app/view/category/category.component.ts
Normal file
71
src/app/view/category/category.component.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {Tag} from '../../class/Tag';
|
||||
import {NzMessageService} from 'ng-zorro-antd';
|
||||
import {PageList} from '../../class/HttpReqAndResp';
|
||||
import {Article} from '../../class/Article';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Location} from '@angular/common';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'view-category',
|
||||
templateUrl: './category.component.html',
|
||||
styleUrls: ['./category.component.less']
|
||||
})
|
||||
export class CategoryComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService,
|
||||
private nzMessageService: NzMessageService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private location: Location,
|
||||
private title: Title) {
|
||||
}
|
||||
|
||||
categoryList: Tag[] = [];
|
||||
private category: Tag;
|
||||
articleList: PageList<Article>;
|
||||
|
||||
name: string;
|
||||
|
||||
ngOnInit() {
|
||||
this.name = this.activatedRoute.snapshot.paramMap.get('category');
|
||||
this.getCategories(this.name == null);
|
||||
if (this.name != null) {
|
||||
this.getArticles(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
getCategories(needGetArticle: boolean) {
|
||||
this.apiService.categories().subscribe(data => {
|
||||
this.categoryList = data.result;
|
||||
this.category = data.result[0];
|
||||
if (needGetArticle) {
|
||||
this.getArticles(this.category.name);
|
||||
this.name = this.category.name;
|
||||
this.title.setTitle('小海博客 | 分类 | ' + this.name);
|
||||
}
|
||||
}, error => {
|
||||
this.nzMessageService.error('出现了错误,原因:' + error.msg);
|
||||
});
|
||||
}
|
||||
|
||||
getArticles(categoryName: string) {
|
||||
this.apiService.articlesByCategory(categoryName).subscribe(data => {
|
||||
this.articleList = data.result;
|
||||
}, error => {
|
||||
this.nzMessageService.error('出现了错误,原因:' + error.msg);
|
||||
});
|
||||
}
|
||||
|
||||
changeCategory(category: Tag) {
|
||||
if (this.name === category.name) {
|
||||
return;
|
||||
}
|
||||
this.category = category;
|
||||
this.name = category.name;
|
||||
this.location.replaceState('categories/' + this.name);
|
||||
this.getArticles(this.name);
|
||||
this.title.setTitle('小海博客 | 分类 | ' + this.name);
|
||||
}
|
||||
}
|
||||
20
src/app/view/category/category.module.ts
Normal file
20
src/app/view/category/category.module.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {CategoryComponent} from './category.component';
|
||||
import {CategoryRoutingModule} from './category-routing.module';
|
||||
import {NzIconModule, NzToolTipModule} from 'ng-zorro-antd';
|
||||
import {IndexModule} from '../index/index.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [CategoryComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CategoryRoutingModule,
|
||||
NzToolTipModule,
|
||||
NzIconModule,
|
||||
IndexModule
|
||||
]
|
||||
})
|
||||
export class CategoryModule {
|
||||
}
|
||||
7
src/app/view/email-verify/email-verify.component.html
Normal file
7
src/app/view/email-verify/email-verify.component.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div>
|
||||
<div id="main">
|
||||
<nz-alert [nzType]="type" [nzMessage]="message"
|
||||
[nzDescription]="desc" nzShowIcon>
|
||||
</nz-alert>
|
||||
</div>
|
||||
</div>
|
||||
6
src/app/view/email-verify/email-verify.component.less
Normal file
6
src/app/view/email-verify/email-verify.component.less
Normal file
@@ -0,0 +1,6 @@
|
||||
#main{
|
||||
width: 60%;
|
||||
height: 50px;
|
||||
margin: 0 auto;
|
||||
padding: 15% 0;
|
||||
}
|
||||
25
src/app/view/email-verify/email-verify.component.spec.ts
Normal file
25
src/app/view/email-verify/email-verify.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
49
src/app/view/email-verify/email-verify.component.ts
Normal file
49
src/app/view/email-verify/email-verify.component.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {ApiService} from '../../api/api.service';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'view-email-verify',
|
||||
templateUrl: './email-verify.component.html',
|
||||
styleUrls: ['./email-verify.component.less']
|
||||
})
|
||||
export class EmailVerifyComponent implements OnInit {
|
||||
|
||||
constructor(private titleService: Title,
|
||||
private router: Router,
|
||||
public routerinfo: ActivatedRoute,
|
||||
private apiService: ApiService) {
|
||||
titleService.setTitle('小海博客 | 邮箱验证');
|
||||
}
|
||||
|
||||
type: string = 'info';
|
||||
message: string = '正在验证,请稍等';
|
||||
desc: string = '';
|
||||
|
||||
|
||||
private email: string;
|
||||
private verifyId: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.email = this.routerinfo.snapshot.queryParams.email;
|
||||
this.verifyId = this.routerinfo.snapshot.queryParams.verifyId;
|
||||
if (this.email == null || this.verifyId == null) {
|
||||
this.type = 'warning';
|
||||
this.message = '数据不全';
|
||||
this.desc = '链接可能被修改了,请重新点击邮箱中的链接,或者重新发送邮件';
|
||||
return;
|
||||
}
|
||||
this.apiService.emailVerify(this.verifyId, this.email).subscribe({
|
||||
next: data => {
|
||||
this.type = 'success';
|
||||
this.message = '验证成功';
|
||||
},
|
||||
error: e => {
|
||||
this.type = 'error';
|
||||
this.message = e.msg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
24
src/app/view/email-verify/email-verify.module.ts
Normal file
24
src/app/view/email-verify/email-verify.module.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Route, RouterModule} from '@angular/router';
|
||||
import {EmailVerifyComponent} from './email-verify.component';
|
||||
import {NzAlertModule} from 'ng-zorro-antd';
|
||||
|
||||
const routes: Route[] = [
|
||||
{path: '**', component: EmailVerifyComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
EmailVerifyComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(routes),
|
||||
NzAlertModule
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
export class EmailVerifyModule {
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<nz-card id="article-card" [nzLoading]="data==null">
|
||||
<h1><a [routerLink]="'/article/'+data.id">{{data.title}}</a></h1>
|
||||
<div>
|
||||
<span *ngIf="showMediaArea" id="article-original" [ngClass]="data.original?'original':'reproduced'">
|
||||
{{data.original ? '原创' : '转载'}}
|
||||
</span>
|
||||
<span *ngIf="showMediaArea" class="badge">
|
||||
<i nz-icon nzType="calendar" nzTheme="outline"></i>
|
||||
<span>{{data.publishDateFormat}}</span>
|
||||
</span>
|
||||
<span *ngIf="showMediaArea" class="badge">
|
||||
<i nz-icon nzType="user" nzTheme="outline"></i>
|
||||
<span>{{data.authorName}}</span>
|
||||
</span>
|
||||
<span *ngIf="showMediaArea" class="badge">
|
||||
<i nz-icon nzType="file" nzTheme="outline"></i>
|
||||
<span>
|
||||
<a [routerLink]="'/categories/'+data.category">{{data.category}}</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<p>{{data.summary}}</p>
|
||||
<span style="float: right;margin-bottom: 10px">
|
||||
<a [routerLink]="'/article/'+data.id">阅读更多<i nz-icon nzType="right" nzTheme="outline"></i></a>
|
||||
</span>
|
||||
<ng-template [ngIf]="showTagArea&&data.tags.length>0">
|
||||
<nz-divider></nz-divider>
|
||||
<div>
|
||||
<span *ngFor="let tag of data.tags">
|
||||
<i nz-icon nzType="tag" nzTheme="outline"></i>
|
||||
<a [routerLink]="'/tags/'+tag">{{tag}}</a>
|
||||
</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</nz-card>
|
||||
@@ -0,0 +1,65 @@
|
||||
@import "../../../../global-variables";
|
||||
|
||||
#article-card {
|
||||
border-radius: 3px;
|
||||
margin-bottom: 45px;
|
||||
|
||||
h1, p, a {
|
||||
word-break: break-all;
|
||||
color: black;
|
||||
}
|
||||
|
||||
h1 {
|
||||
//border-bottom: 1px solid #ececec;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
|
||||
i {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
padding: 1px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 20px;
|
||||
font-size: 1.1em;
|
||||
line-height: 2em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#article-original {
|
||||
padding: 3px 6px;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.original {
|
||||
background: #5eb95e;;
|
||||
}
|
||||
|
||||
.reproduced {
|
||||
background: #f37b1d;;
|
||||
}
|
||||
}
|
||||
|
||||
/* max-width:910px */
|
||||
@media screen and (max-width: @max-width) {
|
||||
span {
|
||||
margin-right: 0 !important;
|
||||
padding: 1px 3px !important;
|
||||
}
|
||||
|
||||
i {
|
||||
padding: 3px !important;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ArticleDetailCardComponent } from './article-detail-card.component';
|
||||
|
||||
describe('ArticleDetailCardComponent', () => {
|
||||
let component: ArticleDetailCardComponent;
|
||||
let fixture: ComponentFixture<ArticleDetailCardComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ArticleDetailCardComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ArticleDetailCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Article} from '../../../../class/Article';
|
||||
import {ColorList} from '../../../../utils/color';
|
||||
|
||||
@Component({
|
||||
selector: 'c-article-detail-card',
|
||||
templateUrl: './article-detail-card.component.html',
|
||||
styleUrls: ['./article-detail-card.component.less']
|
||||
})
|
||||
export class ArticleDetailCardComponent implements OnInit {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
@Input() data: Article;
|
||||
@Input() showMediaArea: boolean;
|
||||
@Input() showTagArea: boolean;
|
||||
|
||||
ngOnInit() {
|
||||
if (this.data == null || this.data.id == null) {
|
||||
throw Error('data 不可为空');
|
||||
}
|
||||
if (this.showMediaArea == null) {
|
||||
// 如果作者名不为空 则显示
|
||||
this.showMediaArea = this.data.authorName != null;
|
||||
}
|
||||
if (this.showTagArea == null) {
|
||||
this.showTagArea = this.data.tags != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<nz-card [nzTitle]="title?titleTmp:null" style="margin-bottom: 50px;">
|
||||
<ng-content></ng-content>
|
||||
</nz-card>
|
||||
|
||||
<ng-template #titleTmp>
|
||||
<div style="text-align: center">{{title}}</div>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CardDetailComponent } from './card-detail.component';
|
||||
|
||||
describe('CardDetailComponent', () => {
|
||||
let component: CardDetailComponent;
|
||||
let fixture: ComponentFixture<CardDetailComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CardDetailComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user