A 添加通知功能

pull/22/head
vapao 2020-01-15 21:12:16 +08:00
parent f947775a93
commit d11c26c5ac
3 changed files with 122 additions and 16 deletions

View File

@ -13,8 +13,8 @@ class NotifyView(View):
def patch(self, request):
form, error = JsonParser(
Argument('id', type=int, help='参数错误')
Argument('ids', type=list, help='参数错误')
).parse(request.body)
if error is None:
Notify.objects.filter(pk=form.id).update(unread=False)
Notify.objects.filter(id__in=form.ids).update(unread=False)
return json_response(error=error)

View File

@ -1,29 +1,105 @@
/**
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
* Copyright (c) <spug.dev@gmail.com>
* Released under the MIT License.
*/
import React from 'react';
import { Layout, Dropdown, Menu, Icon, Avatar } from 'antd';
import { Layout, Dropdown, Menu, List, Icon, Badge, Avatar } from 'antd';
import styles from './layout.module.css';
import http from '../libs/http';
import history from '../libs/history';
import avatar from './avatar.png';
import moment from 'moment';
export default class extends React.Component {
constructor(props) {
super(props);
this.inerval = null;
this.state = {
loading: true,
notifies: [],
read: []
}
}
componentDidMount() {
this.fetch();
this.interval = setInterval(this.fetch, 30000)
}
componentWillUnmount() {
this.interval && clearInterval(this.interval)
}
fetch = () => {
this.setState({loading: true});
http.get('/api/notify/')
.then(res => this.setState({notifies: res, read: []}))
.finally(() => this.setState({loading: false}))
};
handleLogout = () => {
history.push('/');
http.get('/api/account/logout/')
};
handleRead = (e, item) => {
e.stopPropagation();
this.state.read.push(item.id);
this.setState({read: this.state.read});
http.patch('/api/notify/', {ids: [item.id]})
};
handleReadAll = () => {
const ids = this.state.notifies.map(x => x.id);
this.setState({read: ids});
http.patch('/api/notify/', {ids})
};
menu = (
<Menu>
<Menu.Item disabled>
<Icon type="user" style={{marginRight: 10}}/>个人中心
</Menu.Item>
<Menu.Divider/>
<Menu.Item onClick={this.handleLogout}>
<Icon type="logout" style={{marginRight: 10}}/>退出登录
</Menu.Item>
</Menu>
);
notify = () => (
<Menu className={styles.notify}>
<Menu.Item style={{padding: 0, whiteSpace: 'unset'}}>
<List
loading={this.state.loading}
style={{maxHeight: 500, overflow: 'scroll'}}
itemLayout="horizontal"
dataSource={this.state.notifies}
renderItem={item => (
<List.Item className={styles.notifyItem} onClick={e => this.handleRead(e, item)}>
<List.Item.Meta
style={{opacity: this.state.read.includes(item.id) ? 0.4 : 1}}
avatar={<Icon type={item.source} style={{fontSize: 24, color: '#1890ff'}}/>}
title={<span style={{fontWeight: 400, color: '#404040'}}>{item.title}</span>}
description={[
<div key="1" style={{fontSize: 12}}>{item.content}</div>,
<div key="2" style={{fontSize: 12}}>{moment(item['created_at']).fromNow()}</div>
]}/>
</List.Item>
)}/>
{this.state.notifies.length === 0 && (
<div>
<img src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg" alt="not found"/>
<div>暂无未读通知</div>
</div>
)}
<div className={styles.notifyFooter} onClick={() => this.handleReadAll()}>全部 已读</div>
</Menu.Item>
</Menu>
);
render() {
const menu = (
<Menu>
<Menu.Item disabled>
<Icon type="user" style={{marginRight: 10}}/>个人中心
</Menu.Item>
<Menu.Divider/>
<Menu.Item onClick={this.handleLogout}>
<Icon type="logout" style={{marginRight: 10}}/>退出登录
</Menu.Item>
</Menu>
);
const {notifies, read} = this.state;
return (
<Layout.Header style={{padding: 0}}>
<div className={styles.header}>
@ -31,13 +107,22 @@ export default class extends React.Component {
<Icon type={this.props.collapsed ? 'menu-unfold' : 'menu-fold'}/>
</div>
<div className={styles.right}>
<Dropdown overlay={menu}>
<Dropdown overlay={this.menu}>
<span className={styles.action}>
<Avatar size="small" src={avatar} style={{marginRight: 8}}/>
{localStorage.getItem('nickname')}
</span>
</Dropdown>
</div>
<div className={styles.right}>
<Dropdown overlay={this.notify} trigger={['click']}>
<span className={styles.trigger}>
<Badge count={notifies.length - read.length}>
<Icon type="notification" style={{fontSize: 16}}/>
</Badge>
</span>
</Dropdown>
</div>
</div>
</Layout.Header>
)

View File

@ -67,6 +67,27 @@
.action:hover {
background: rgb(233, 247, 254);
}
.notify {
width: 350px;
padding: 0;
}
.notify :global(.ant-dropdown-menu-item:hover) {
background-color: #fff;
}
.notifyItem {
align-items: center;
cursor: pointer;
padding: 12px 24px;
}
.notifyItem:hover {
background-color: rgb(233, 247, 254);
}
.notifyFooter {
line-height: 46px;
text-align: center;
cursor: pointer;
border-top: 1px solid #e8e8e8;
}
.footer {
margin: 48px 0 24px;