mirror of
https://github.com/SunnyQjm/sunny-blog.git
synced 2026-10-05 01:15:39 +08:00
add: 支持博文的列表查看和删除
This commit is contained in:
1 parent
7c3706b298
commit
6e676fb689
14 files changed
+357
-81
No files matched your search
@@ -15,6 +15,37 @@ interface EditableTagGroupComponentState {
|
||||
inputValue: string
|
||||
}
|
||||
|
||||
const colors = [
|
||||
'magenta',
|
||||
'red',
|
||||
'volcano',
|
||||
'orange',
|
||||
'gold',
|
||||
'lime',
|
||||
'green',
|
||||
'cyan',
|
||||
'blue',
|
||||
'geekblue',
|
||||
'purple'
|
||||
];
|
||||
|
||||
function getHashCode(str: string, caseSensitive: boolean = false) {
|
||||
if (!caseSensitive) {
|
||||
str = str.toLowerCase();
|
||||
}
|
||||
// 1315423911=b'1001110011001111100011010100111'
|
||||
let hash = 1315423911, i, ch;
|
||||
for (i = str.length - 1; i >= 0; i--) {
|
||||
ch = str.charCodeAt(i);
|
||||
hash ^= ((hash << 5) + ch + (hash >> 2));
|
||||
}
|
||||
return (hash & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
function getRandColor(tag: string) {
|
||||
return colors[getHashCode(tag) % colors.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* 可编辑,带动画的标签组
|
||||
*/
|
||||
@@ -87,6 +118,7 @@ class EditableTagGroupComponent extends React.Component<EditableTagGroupComponen
|
||||
e.preventDefault();
|
||||
this.handleClose(tag);
|
||||
}}
|
||||
color={getRandColor(tag)}
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #555;
|
||||
&:hover{
|
||||
color: #555;
|
||||
}
|
||||
}
|
||||
//a {
|
||||
// text-decoration: none;
|
||||
// color: #555;
|
||||
// &:hover{
|
||||
// color: #555;
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface Comment {
|
||||
id: number,
|
||||
name: string,
|
||||
email: string,
|
||||
content: string,
|
||||
}
|
||||
@@ -4,3 +4,11 @@ export interface BaseResponseBody<T> {
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
|
||||
export interface BaseListData<T> {
|
||||
list: T[],
|
||||
total: number,
|
||||
page: number,
|
||||
size: number,
|
||||
}
|
||||
+25
-3
@@ -3,14 +3,19 @@ import {BaseResponseBody} from "@/data/interface/response.interface";
|
||||
import {StatusCode} from "@/data/network/status-code";
|
||||
import {User} from "@/data/user";
|
||||
import router from "umi/router";
|
||||
import {CreatePostParam} from "@/data/param/request.param";
|
||||
import {CreatePostParam, DelPostParam, GetPostsParam} from "@/data/param/request.param";
|
||||
import {message} from "antd";
|
||||
|
||||
export const API = {
|
||||
INFO: {
|
||||
DEFAULT_PAGE_SIZE: 10,
|
||||
|
||||
login: '/login',
|
||||
register: '/register',
|
||||
getUserInfo: '/admin/getUserInfo',
|
||||
createPost: '/admin/create-post',
|
||||
delPost: 'admin/del-post',
|
||||
getPosts: '/post/list',
|
||||
},
|
||||
global: {
|
||||
token: ''
|
||||
@@ -58,12 +63,14 @@ export const API = {
|
||||
case StatusCode.NEED_ADMIN_PERMISSION:
|
||||
case StatusCode.COMMENT_ERROR:
|
||||
case StatusCode.NEED_LOGIN:
|
||||
message.error(responseData.message);
|
||||
break;
|
||||
case StatusCode.UN_AUTHORIZED:
|
||||
message.warning('请先登录');
|
||||
router.replace('/login');
|
||||
break;
|
||||
}
|
||||
if(responseData.statusCode === StatusCode.SUCCESS) {
|
||||
if (responseData.statusCode === StatusCode.SUCCESS) {
|
||||
return responseData.data;
|
||||
} else {
|
||||
return Promise.reject(responseData);
|
||||
@@ -112,7 +119,22 @@ export const API = {
|
||||
*/
|
||||
createPost(createPostParam: CreatePostParam) {
|
||||
return API.eastPost(API.INFO.createPost, createPostParam);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取博文列表
|
||||
* @param getPostsParam
|
||||
*/
|
||||
getPosts(getPostsParam: GetPostsParam) {
|
||||
return API.easyGet(API.INFO.getPosts, getPostsParam);
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除博文
|
||||
* @param delPostParam
|
||||
*/
|
||||
delPost(delPostParam: DelPostParam) {
|
||||
return API.eastPost(API.INFO.delPost, delPostParam);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,3 +3,16 @@ export interface CreatePostParam {
|
||||
content: string,
|
||||
tags?: string[],
|
||||
}
|
||||
|
||||
export interface GetListParam {
|
||||
page: number,
|
||||
size: number
|
||||
}
|
||||
|
||||
export interface GetPostsParam extends GetListParam {
|
||||
tagId?: string,
|
||||
}
|
||||
|
||||
export interface DelPostParam {
|
||||
postId: number
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
import {Tag} from "@/data/tag";
|
||||
import {Comment} from "@/data/comment";
|
||||
import {User} from "@/data/user";
|
||||
|
||||
export interface Post {
|
||||
id: number,
|
||||
title: string,
|
||||
content: string,
|
||||
tags: Tag[],
|
||||
comments: Comment[],
|
||||
owner: User,
|
||||
createdAt: number,
|
||||
updatedAt: number,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface Tag {
|
||||
id: number,
|
||||
tagName: string,
|
||||
}
|
||||
+10
-11
@@ -4,17 +4,14 @@ import {User} from "@/data/user";
|
||||
import router from 'umi/router';
|
||||
import {message} from 'antd'
|
||||
import {LocalStorageKeys, LocalStorageManager} from "@/manager/storage.manager";
|
||||
import {CreatePostParam} from "@/data/param/request.param";
|
||||
|
||||
export interface AdminModelState {
|
||||
user: User,
|
||||
loginIng: boolean
|
||||
}
|
||||
import {CreatePostParam, DelPostParam, GetPostsParam} from "@/data/param/request.param";
|
||||
import {Post} from "@/data/post";
|
||||
|
||||
export default {
|
||||
namespace: 'admin',
|
||||
state: {
|
||||
user: null,
|
||||
posts: [],
|
||||
},
|
||||
reducers: {
|
||||
/**
|
||||
@@ -23,7 +20,7 @@ export default {
|
||||
* @param action
|
||||
*/
|
||||
updateUser(state: any, action: Action<User>) {
|
||||
if(action.data && action.data.access_token) {
|
||||
if (action.data && action.data.access_token) {
|
||||
API.global.token = action.data.access_token;
|
||||
}
|
||||
return {
|
||||
@@ -41,12 +38,14 @@ export default {
|
||||
...state,
|
||||
user: null
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
effects: {
|
||||
* init(action: Action<any>, operators: Operators) {
|
||||
const user = yield LocalStorageManager.getObj(LocalStorageKeys.USER);
|
||||
if(user) {
|
||||
if (user) {
|
||||
yield operators.put({
|
||||
type: 'updateUser',
|
||||
data: user
|
||||
@@ -79,8 +78,8 @@ export default {
|
||||
|
||||
* createPost(action: Action<CreatePostParam>, operators: Operators) {
|
||||
yield operators.call(API.createPost, action.data);
|
||||
message.info('发表成功');
|
||||
}
|
||||
message.success('发表成功');
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
import React from "react";
|
||||
import Antd, {Divider, message, Table, Popconfirm} from 'antd';
|
||||
import {connect} from 'dva';
|
||||
import withRouter from 'umi/withRouter';
|
||||
import './index.scss'
|
||||
import RouterTypes from "umi/routerTypes";
|
||||
import {Post} from "@/data/post";
|
||||
import {Tag} from "@/data/tag";
|
||||
import {dateFormat} from "@/utils/date.util";
|
||||
|
||||
interface PostManagerPageProps {
|
||||
|
||||
interface PostManagerPageProps extends RouterTypes {
|
||||
dispatch: any,
|
||||
loading: boolean,
|
||||
posts: Post[],
|
||||
total: number,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
}
|
||||
|
||||
interface PostManagerPageState {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// // rowSelection object indicates the need for row selection
|
||||
// const rowSelection = {
|
||||
// onChange: (selectedRowKeys: any, selectedRows: any) => {
|
||||
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
||||
// },
|
||||
// getCheckboxProps: (record: any) => ({
|
||||
// disabled: record.name === 'Disabled User', // Column configuration not to be checked
|
||||
// name: record.name,
|
||||
// }),
|
||||
// };
|
||||
|
||||
|
||||
/**
|
||||
* 博文管理页面
|
||||
*/
|
||||
@@ -17,13 +42,146 @@ class PostManagerPage extends React.Component<PostManagerPageProps, PostManagerP
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.props.dispatch({
|
||||
type: 'post-manager/getPosts',
|
||||
data: {
|
||||
page: 1,
|
||||
size: this.props.pageSize
|
||||
}
|
||||
});
|
||||
this.handleDelete = this.handleDelete.bind(this);
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理删除博文
|
||||
*/
|
||||
handleDelete(postId: number) {
|
||||
message.loading(`删除: ${postId}`);
|
||||
this.props.dispatch({
|
||||
type: 'post-manager/delPost',
|
||||
data: postId
|
||||
})
|
||||
}
|
||||
|
||||
handlePageChange(newPage: number, newPageSize: number) {
|
||||
this.props.dispatch({
|
||||
type: 'post-manager/getPosts',
|
||||
data: {
|
||||
page: newPage,
|
||||
size: newPageSize
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
columns = [
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
dataIndex: 'title',
|
||||
render: (text: any) => <a>{text}</a>,
|
||||
},
|
||||
{
|
||||
title: '内容',
|
||||
key: 'content',
|
||||
dataIndex: 'content',
|
||||
render: (content: string) => <span>
|
||||
{
|
||||
content.length > 100 ?
|
||||
content.substr(0, 100)
|
||||
:
|
||||
content
|
||||
}
|
||||
</span>
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
key: 'tags',
|
||||
dataIndex: 'tags',
|
||||
render: (tags: Tag[]) => (
|
||||
<span>
|
||||
{tags.map(tag => {
|
||||
let color = tag.tagName.length > 5 ? 'geekblue' : 'green';
|
||||
return (
|
||||
<Antd.Tag color={color} key={tag.id}>
|
||||
{tag.tagName}
|
||||
</Antd.Tag>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
dataIndex: 'createdAt',
|
||||
render: (date: string) => {
|
||||
return (
|
||||
<span>{dateFormat("YYYY-mm-dd HH:MM", new Date(date))}</span>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
ket: 'updatedAt',
|
||||
dataIndex: 'updatedAt',
|
||||
render: (date: string) => {
|
||||
return (
|
||||
<span>{dateFormat("YYYY-mm-dd HH:MM", new Date(date))}</span>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (text: string, record: Post) => (
|
||||
<span>
|
||||
<a>预览</a>
|
||||
<Divider type="vertical"/>
|
||||
<a>编辑</a>
|
||||
<Divider type="vertical"/>
|
||||
<Popconfirm title='确认删除?' okText='确认' cancelText='取消' onConfirm={() => this.handleDelete(record.id)}>
|
||||
<a style={{color: 'red'}}>删除</a>
|
||||
</Popconfirm>
|
||||
</span>
|
||||
),
|
||||
}
|
||||
];
|
||||
|
||||
render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined {
|
||||
const {
|
||||
loading,
|
||||
posts,
|
||||
page,
|
||||
pageSize,
|
||||
total
|
||||
} = this.props;
|
||||
const data = posts.map(post => {
|
||||
return {
|
||||
...post,
|
||||
key: post.id
|
||||
}
|
||||
});
|
||||
const pagination: any = {
|
||||
pageSize,
|
||||
total: total,
|
||||
current: page,
|
||||
onChange: this.handlePageChange
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
PostManager Page
|
||||
<Table columns={this.columns} dataSource={data} useFixedHeader loading={loading} pagination={pagination}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PostManagerPage
|
||||
function mapStateToProps(state: any) {
|
||||
return {
|
||||
...state['post-manager'],
|
||||
loading: state.loading.effects['post-manager/getPosts'],
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(PostManagerPage))
|
||||
@@ -0,0 +1,64 @@
|
||||
import {Action, Operators} from "@/data/interface/dva.interface";
|
||||
import {DelPostParam, GetPostsParam} from "@/data/param/request.param";
|
||||
import {API} from "@/data/network/api";
|
||||
import {message} from "antd";
|
||||
import {Post} from "@/data/post";
|
||||
import {BaseListData} from "@/data/interface/response.interface";
|
||||
|
||||
export default {
|
||||
namespace: 'post-manager',
|
||||
state: {
|
||||
posts: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: API.INFO.DEFAULT_PAGE_SIZE
|
||||
},
|
||||
reducers: {
|
||||
updatePosts(state: any, action: Action<BaseListData<Post>>) {
|
||||
return {
|
||||
...state,
|
||||
total: action.data.total,
|
||||
posts: action.data.list,
|
||||
page: action.data.page,
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
removePost(state: any, action: Action<number>) {
|
||||
return {
|
||||
...state,
|
||||
posts: state.posts.filter((post: Post) => post.id !== action.data)
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: {
|
||||
* getPosts(action: Action<GetPostsParam>, operators: Operators) {
|
||||
const postList = yield operators.call(API.getPosts, action.data);
|
||||
console.log(postList);
|
||||
yield operators.put({
|
||||
type: 'updatePosts',
|
||||
data: {
|
||||
...postList,
|
||||
...action.data,
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
* delPost(action: Action<DelPostParam>, operators: Operators) {
|
||||
const result = yield operators.call(API.delPost, {
|
||||
postId: action.data
|
||||
});
|
||||
message.destroy();
|
||||
if (result) { // 删除成功
|
||||
yield operators.put({
|
||||
type: 'removePost',
|
||||
data: action.data
|
||||
});
|
||||
message.success('删除成功')
|
||||
} else { // 删除失败
|
||||
message.error('删除失败')
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
.edit-post-page {
|
||||
&__title-input {
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from "react";
|
||||
import MdEditor from "for-editor";
|
||||
import {
|
||||
Input
|
||||
} from 'antd';
|
||||
import './index.scss'
|
||||
|
||||
interface EditPostPageProps {
|
||||
|
||||
}
|
||||
|
||||
interface EditPostPageState {
|
||||
value: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑和发布博文的界面
|
||||
*/
|
||||
class EditPostPage extends React.Component<EditPostPageProps, EditPostPageState> {
|
||||
constructor(props: EditPostPageProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: ''
|
||||
};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
handleChange(value: string) {
|
||||
this.setState({
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined {
|
||||
const {
|
||||
value
|
||||
} = this.state;
|
||||
return (
|
||||
<div className='edit-post-page'>
|
||||
<Input placeholder='在此输入文章标题' className='edit-post-page__title-input'/>
|
||||
<MdEditor value={value} onChange={this.handleChange}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditPostPage
|
||||
@@ -0,0 +1,19 @@
|
||||
export const dateFormat = (fmt: any, date: any) => {
|
||||
let ret;
|
||||
let opt: any = {
|
||||
"Y+": date.getFullYear().toString(), // 年
|
||||
"m+": (date.getMonth() + 1).toString(), // 月
|
||||
"d+": date.getDate().toString(), // 日
|
||||
"H+": date.getHours().toString(), // 时
|
||||
"M+": date.getMinutes().toString(), // 分
|
||||
"S+": date.getSeconds().toString() // 秒
|
||||
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
||||
};
|
||||
for (let k in opt) {
|
||||
ret = new RegExp("(" + k + ")").exec(fmt);
|
||||
if (ret) {
|
||||
fmt = fmt.replace(ret[1], (ret[1].length === 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
|
||||
}
|
||||
}
|
||||
return fmt;
|
||||
};
|
||||
Reference in new issue
Block a user