Merge branch 'feature_notification' of https://git.trustie.net/tongChong/forgeplus-react into feature_notification_xiesi

This commit is contained in:
谢思 2021-09-14 16:19:41 +08:00
commit c0a4432c18
7 changed files with 525 additions and 154 deletions

View File

@ -0,0 +1,108 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Icon } from 'antd';
import _ from 'lodash';
import Nodata from '../Nodata';
class PullRefresh extends Component {
constructor(props) {
super(props);
this.state = {
}
this.pullRef = {};
//
this.onScrollList = _.throttle(this.handleScroll, 200, {
leading: false,
trailing: true,
});
}
componentDidMount() {
let dom = document.querySelector('.pull-refresh-wrap');
dom && dom.addEventListener('scroll', this.onScrollList);
}
componentWillUnmount() {
let dom = document.querySelector('.pull-refresh-wrap');
dom && dom.removeEventListener('scroll', this.onScrollList)
}
handleScroll = () => {
if (this.props.count < this.props.pageSize) return;
if (this.props.type === 1 || this.props.type === 2) return;
const wrap = this.pullRef;
const currentScroll = wrap.scrollTop + wrap.clientHeight
//
if (currentScroll >= (wrap.scrollHeight - 10)) {
this.loadData()
}
}
handleLoadClick = () => {
this.loadData();
}
loadData = () => {
this.props.onPullRefresh()
}
renderLoading() {
switch (this.props.type) {
case 0: //
return <div className='text-center' onClick={this.handleLoadClick}>显示更多</div>
case 1: //
return (
<div className='text-center'>
<Icon type="loading" />
<span className='text-center'>加载中...</span>
</div>
)
case 2: //
return <div className='text-center'>没有更多了</div>
default:
return <div className='text-center'>没有更多了</div>
}
}
render() {
const { className, count, children } = this.props;
return (
<div
className={`pull-refresh-wrap ${className}`}
ref={dom => { this.pullRef = dom }}
>
{children}
{
count < 1 && <Nodata _html="暂无未读消息"/>
}
{/* 大于分页数据才显示loading */}
{/* {this.props.count >= this.props.pageSize ? this.renderLoading() : null} */}
</div>
)
}
}
PullRefresh.propTypes = {
className: PropTypes.string,
children: PropTypes.any,
onPullRefresh: PropTypes.func.isRequired,
type: PropTypes.oneOf([0, 1, 2]),
count: PropTypes.number.isRequired,
pageSize: PropTypes.number.isRequired,
}
export default PullRefresh

View File

@ -48,6 +48,7 @@ class NewHeader extends Component {
settings: null, settings: null,
visiblemyss: false, visiblemyss: false,
openSearch:false, openSearch:false,
visible:false, //浮动消息框展示控制
} }
} }
componentDidMount() { componentDidMount() {
@ -93,9 +94,6 @@ class NewHeader extends Component {
this.setState({ this.setState({
user: newProps.user user: newProps.user
}) })
if (newProps.Headertop !== undefined) {
old_url = newProps.Headertop.old_url
}
} }
educoderlogin = () => { educoderlogin = () => {
@ -277,8 +275,12 @@ class NewHeader extends Component {
) )
} }
handleVisibleChange = visible => {
this.setState({ visible });
};
render() { render() {
const { match} = this.props; const { match ,hisroty ,showNotification} = this.props;
let current_user = this.props.user; let current_user = this.props.user;
let { let {
AccountProfiletype, AccountProfiletype,
@ -287,6 +289,7 @@ class NewHeader extends Component {
headtypesonClickbool, headtypesonClickbool,
headtypess, headtypess,
settings, settings,
visible,
} = this.state; } = this.state;
/*用户名称 用户头像url*/ /*用户名称 用户头像url*/
let activeIndex = false; let activeIndex = false;
@ -366,6 +369,7 @@ class NewHeader extends Component {
let search_url = settings && settings.common && settings.common.search; let search_url = settings && settings.common && settings.common.search;
let notice_url = settings && settings.common && settings.common.notice; let notice_url = settings && settings.common && settings.common.notice;
console.log(current_user);
return ( return (
<div className="newHeaders" id="nHeader"> <div className="newHeaders" id="nHeader">
<div className="headerContent"> <div className="headerContent">
@ -428,7 +432,6 @@ class NewHeader extends Component {
} }
</div> </div>
<div className="head-right"> <div className="head-right">
{/* {search_url ? this.SearchInput(openSearch,search_url):""} */}
{ search_url && <HeadSearch {...this.props}/>} { search_url && <HeadSearch {...this.props}/>}
{ {
current_user && (current_user.main_site || current_user.login) && (settings && settings.add && settings.add.length>0)? current_user && (current_user.main_site || current_user.login) && (settings && settings.add && settings.add.length>0)?
@ -437,13 +440,18 @@ class NewHeader extends Component {
</Dropdown>:"" </Dropdown>:""
} }
{/* {current_user && current_user.login && notice_url ? */}
{current_user && current_user.login ? {current_user && current_user.login ?
<Popover placement={`bottomRight`} content={<NoticeContent/>}> <Popover
<a href={`/settings/notice/myNotice`} > overlayClassName="notice-popover"
<i className="iconfont icon-xiaoxilingdang color-grey-6 ml15 mr15"></i> placement={`bottomRight`}
<span className="newslight" style={{ display: this.props.Headertop === undefined ? "none" : this.props.Headertop.new_message === true ? "block" : "none" }}> content={<NoticeContent current_user={current_user} showNotification={showNotification}/>}
</span> visible={visible}
onVisibleChange={this.handleVisibleChange}
>
<a className="message-icon" href={`/settings/notice/myNotice`}>
<Badge count={current_user.message_unread_total}>
<i className="iconfont icon-xiaoxilingdang color-grey-6 ml15 mr15"></i>
</Badge>
</a> </a>
</Popover> </Popover>
: "" : ""

View File

@ -1,157 +1,227 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Badge, Menu} from 'antd'; import { Badge, Menu } from 'antd';
import { Link } from 'react-router-dom';
import axios from 'axios';
import AppPullRefresh from './AppPullRefresh';
import './header.scss'; import './header.scss';
import '../SecuritySetting/notice/manager/Index.scss'; import '../SecuritySetting/notice/manager/Index.scss';
import '../SecuritySetting/Index.scss'; import '../SecuritySetting/Index.scss';
import '../SecuritySetting/notice/myNotice/Index.scss' import '../SecuritySetting/notice/myNotice/Index.scss';
import { timeAgo } from '../../common/DateUtil';
import { Link } from 'react-router-dom';
import Axios from 'axios';
function NoticeContent(props) {
const[noticeType,setNoticeType] = useState("0"); function NoticeContent({ showNotification, current_user: { login } }) {
const[notice_unread_count,setNotice_unread_count]=useState();// const [initialize, setInitialize] = useState(true);
const[letter_unread_count,setLetter_unread_count]=useState(10);// const [noticeType, setNoticeType] = useState("notification");
const[at_unread_count,setAt_unread_count]=useState();//@ const [letterUnreadCount, setLetterUnreadCount] = useState(0);//
const [message_list, setMessage_list] = useState([]);
const [noticeUnreadCount, setNoticeUnreadCount] = useState(0);//
const [noticePage, setNoticePage] = useState(0);
const [noticeUnreadList, setNoticeUnreadList] = useState([]);//
const [atUnreadCount, setAtUnreadCount] = useState();//@
const [atPage, setAtPage] = useState(0);
const [atUnreadList, setAtUnreadList] = useState([]);//@
const [reload, setReload] = useState(0);
useEffect(() => { useEffect(() => {
const params = { const params = {
type:noticeType==="0"?"notification":noticeType==="2"?"atme":"", type: noticeType,
limit: 20, limit: 10,
page: 0, page: noticeType === "notification" ? noticePage : noticeType === "atme" ? atPage : "",
status: 1,
} }
getMessageList(params); getMessageList(params);
}, [noticeType]) }, [noticePage, atPage]);
useEffect(() => {
const params = {
type: noticeType,
limit: 10,
page: 0,
status: 1,
};
if (initialize) {
params.type = "atme"
}
getMessageList(params);
}, [reload]);
function getMessageList(params) { function getMessageList(params) {
console.log(params); axios.get(`/users/${login}/messages.json`, {
Axios.get(`/users/yystopf/messages.json`, {
params: params, params: params,
}).then((response) => { }).then((response) => {
setNotice_unread_count(response.data.unread_notification); if (response && response.data) {
setAt_unread_count(response.data.unread_atme); setNoticeUnreadCount(response.data.unread_notification);
setMessage_list(response.data.messages); setAtUnreadCount(response.data.unread_atme);
console.log(message_list); if (params.type === "notification") {
let list = response.data.messages;
if (params.page !== 0) {
list = [...noticeUnreadList, ...list];
}
setNoticeUnreadList(list);
if (initialize) {
// tab
setInitialize(false);
if (response.data.unread_notification === 0 && response.data.unread_atme !== 0) {
setNoticeType("atme");
}
}
} else if (params.type === "atme") {
let list = response.data.messages;
if (params.page !== 0) {
list = [...atUnreadList, ...list];
}
setAtUnreadList(list);
}
}
}) })
} }
const[letter_unread_list,setLetter_unread_list]=useState([
{ function readAll() {
id: 122, axios.post(`/users/${login}/messages/read.json`, {
read: 0, //01 type: noticeType,
send_name: "蒋宇航", // ids: [-1]
send_login: "jiangYuHang", //login }).then((response) => {
content: "私信内容", // let data = response.data;
create_time: "2019-03-04 18:08", // if (!data) return;
}, if (data.status === 0) {
{ setReload(Math.random());
id: 122, } else {
read: 0, //01 showNotification(data.message);
send_name: "景霞", // }
send_login: "jiangYuHang", //login });
content: "最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性、页面性能等方面进行深度优化提供简单控制台。", // }
create_time: "2019-03-04 18:08", //
}, // const [letter_unread_list, setLetter_unread_list] = useState([
{ // {
id: 122, // id: 122,
read: 0, //01 // read: 0, //01
send_name: "陈银花", // // send_name: "", //
send_login: "jiangYuHang", //login // send_login: "jiangYuHang", //login
content: "A Vue 3 Component Library. Fairly Complete. Customizable Themes. Uses TypeScript. Not too Slow.", // // content: "", //
create_time: "2019-03-04 18:08", // // create_time: "2019-03-04 18:08", //
}, // },
{ // ]);
id: 122,
read: 0, //01 function readItem(item) {
send_name: "蒋宇航", // axios.post(`/users/${login}/messages/read.json`, {
send_login: "jiangYuHang", //login type: noticeType,
content: "您好?", // ids: [item.id]
create_time: "2019-03-04 18:08", // }).then((response) => {
}, let data = response.data;
{ if (!data) return;
id: 122, if (data.status === 0) {
read: 0, //01 changeReadMark(item);
send_name: "蒋宇航", // item.notification_url && window.open(item.notification_url);
send_login: "jiangYuHang", //login } else {
content: "Open-source high-performance RISC-V processor", // showNotification(data.message);
create_time: "2019-03-04 18:08", // }
}, });
{ }
id: 122,
read: 0, //01 function changeReadMark(item) {
send_name: "蒋宇航", // if (item.type === "notification") {
send_login: "jiangYuHang", //login let list = noticeUnreadList.slice();
content: "构建卷积神经网络,训练模型,预测模型效果。", // let index = noticeUnreadList.indexOf(item);
create_time: "2019-03-04 18:08", // list[index].status = 2;
setNoticeUnreadList(list);
} else if (item.type === "atme") {
let list = atUnreadList.slice();
let index = atUnreadList.indexOf(item);
list[index].status = 2;
setAtUnreadList(list);
} }
]); }
return( return (
<div className="messageHoverDiv notice01"> <div className="messageHoverDiv notice01">
<div className="sshHead hoverNotice-head"> <div className="sshHead hoverNotice-head">
<Menu mode="horizontal" selectedKeys={noticeType} onClick={(e)=>setNoticeType(e.key)}> <Menu mode="horizontal" selectedKeys={noticeType} onClick={(e) => setNoticeType(e.key)}>
<Menu.Item key="0"><Badge count={notice_unread_count}>系统通知</Badge></Menu.Item> <Menu.Item key="notification"><Badge count={noticeUnreadCount}>系统通知</Badge></Menu.Item>
<Menu.Item key="1" id="item-private"><Badge count={letter_unread_count}>私信</Badge></Menu.Item> {/* <Menu.Item key="1" id="item-private"><Badge count={letterUnreadCount}>私信</Badge></Menu.Item> */}
<Menu.Item key="2"><Badge count={at_unread_count}>@</Badge></Menu.Item> <Menu.Item key="atme"><Badge count={atUnreadCount}>@</Badge></Menu.Item>
</Menu> </Menu>
</div> </div>
<div className="hoverNotice-body">
{message_list.map(item=>{
//
if(noticeType ==="0" && item.type === "notification" && item.status===1){
let contentStr = item.content.endsWith("</b>") && item.content.length>=50 && item.content.replace("</b>","").substr(0,40)+"...";
// let iconName = item.type===1?"icon-xitongtongzhiicon":item.type===2?"icon-xiaoxi2":item.type===3?"icon-yixiuicon1":item.type===4?"icon-hebingqingqiuicon":item.type===5?"icon-lichengbeiicon":"icon-daimakuicon1";
return(
<div className="noticeCont-back">
<div className="noticeCont" style={{height:item.content.length>30 && item.content.length<=34?'65px':""}}>
<Badge color="#FA2020" />
<i className="iconfont icon-yixiuicon1"></i>
<div className="noticeCont-text">
<span dangerouslySetInnerHTML={{__html:contentStr?contentStr: item.content.length>=48?item.content.substr(0,48)+"...":item.content}}></span>
<span className="timeSpan">{item.time_ago}</span>
</div>
</div>
</div>
)
}else if(noticeType ==="2" && item.type === "atme" && item.status===1){
// @
return(
<div className="noticeCont-back">
<div className="noticeCont" style={{height:item.content.length>30 && item.content.length<=42?'65px':""}}>
<Badge color="#FA2020" />
<div className="noticeCont-text">
<span dangerouslySetInnerHTML={{__html:item.content.length>=56?item.content.substr(0,56)+"...@我":item.content}}></span>
<span className="timeSpan">{item.time_ago}</span>
</div>
</div>
</div>
)
}
})}
{/* 私信 */} {/* 系统通知 */}
{noticeType==="1"?letter_unread_list.length>0?letter_unread_list.map(item=>{ {noticeType === "notification" && <AppPullRefresh
return( className='hoverNotice-body' // className
<div className="noticeCont-back"> onPullRefresh={() => { setNoticePage(noticePage + 1); }} //ajaxfunction
<div className="noticeCont" style={{height:item.content.length>=30 && item.content.length<=34?'65px':""}}> // type={2} //
count={noticeUnreadCount} //
pageSize={10} //
>
{
noticeUnreadList.map(item => {
let contentStr = item.content.endsWith("</b>") && item.content.length >= 50 && item.content.replace("</b>", "").substr(0, 40) + "...";
return (
<div key={item.id + Math.random()} className="noticeCont-back" onClick={() => { readItem(item) }}>
<div className="noticeCont" style={{ height: item.content.length > 30 && item.content.length <= 34 ? '65px' : "" }}>
<span style={{ visibility: item.status === 1 ? 'visible' : 'hidden' }}>
<Badge color="#FA2020" />
</span>
<i className="iconfont icon-yixiuicon1"></i>
<div className="noticeCont-text">
<span dangerouslySetInnerHTML={{ __html: contentStr ? contentStr : item.content.length >= 48 ? item.content.substr(0, 48) + "..." : item.content }}></span>
<span className="timeSpan">{item.time_ago}</span>
</div>
</div>
</div>
)
})
}
</AppPullRefresh>
}
{/* @我 */}
{noticeType === "atme" && <AppPullRefresh
className='hoverNotice-body' // className
onPullRefresh={() => { setAtPage(atPage + 1); }} //ajaxfunction
// type={1} //
count={atUnreadCount} //
pageSize={10} //
>
{atUnreadList.map(item => {
return (
<div key={item.id + Math.random()} className="noticeCont-back" onClick={() => { readItem(item) }}>
<div className="noticeCont" style={{ height: item.content.length > 30 && item.content.length <= 42 ? '65px' : "" }}>
<span style={{ visibility: item.status === 1 ? 'visible' : 'hidden' }}>
<Badge color="#FA2020" />
</span>
<div className="noticeCont-text">
<span dangerouslySetInnerHTML={{ __html: item.content.length >= 50 ? item.content.substr(0, 50) + "...@我" : item.content }}></span>
<span className="timeSpan">{item.time_ago}</span>
</div>
</div>
</div>
)
})
}
</AppPullRefresh>
}
{/* 私信 */}
{/* {noticeType === "1" ? letter_unread_list.length > 0 ? letter_unread_list.map(item => {
return (
<div className="noticeCont-back">
<div className="noticeCont" style={{ height: item.content.length >= 30 && item.content.length <= 34 ? '65px' : "" }}>
<Badge color="#FA2020" /> <Badge color="#FA2020" />
<div className="noticeCont-text"> <div className="noticeCont-text">
<span>{item.send_name}</span> <span>{item.send_name}</span>
<span className="boldSpan" dangerouslySetInnerHTML={{__html:item.content.length>=50?item.content.substr(0,50)+"...":item.content}}></span> <span className="boldSpan" dangerouslySetInnerHTML={{ __html: item.content.length >= 50 ? item.content.substr(0, 50) + "..." : item.content }}></span>
<span className="timeSpan">{item.create_time?timeAgo(item.create_time):"刚刚"}</span> <span className="timeSpan">{item.create_time ? timeAgo(item.create_time) : "刚刚"}</span>
</div> </div>
</div> </div>
</div> </div>
) )
}):"暂无数据":""} }) : "暂无数据" : ""} */}
</div> <div className="hoverNotice-buttom">
<div className="hoverNotice-buttom"> <Link to="/settings/notice/myNotice">全部消息</Link>
<Link to="/settings/notice/myNotice">全部消息</Link> <a onClick={readAll}>所有{noticeType === "notification" ? "系统消息" : noticeType === "letter" ? "私信" : "@我"}一键已读</a>
<a>所有{noticeType==="0"?"系统消息":noticeType==="1"?"私信":"@我"}一键已读</a>
</div>
</div> </div>
</div>
) )
} }
export default NoticeContent; export default NoticeContent;

View File

@ -129,19 +129,26 @@
} }
} }
//popover小尖尖 // 右上角小铃铛单独样式
.ant-popover-arrow{ .notice-popover{
display: none; //popover小尖尖
.ant-popover-arrow{
display: none;
}
//popover框
.ant-popover-inner-content {
width: 386px;
height: 446px;
box-shadow: 0px 4px 8px 2px rgba(212, 212, 212, 0.5);
border-radius: 4px;
margin-top: -10px;
padding: 12px 1px 12px 0;
}
} }
//popover框 .messageHoverDiv .ant-menu-item{
.ant-popover-inner-content { margin-right: 24px !important;
width: 386px;
height: 446px;
box-shadow: 0px 4px 8px 2px rgba(212, 212, 212, 0.5);
border-radius: 4px;
margin-top: -10px;
padding: 12px 1px 12px 0;
} }
.hoverNotice-head{ .hoverNotice-head{
@ -164,23 +171,37 @@
font-weight: 400; font-weight: 400;
text-shadow: 0.5px 0 0 #333; text-shadow: 0.5px 0 0 #333;
} }
.none_panels{
height: 100%;
}
} }
.message-icon{
position: relative;
.ant-scroll-number{
right:12px;
}
}
.hoverNotice-buttom{ .hoverNotice-buttom{
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 12px 18px; padding: 12px 18px;
a{ a{
color: #466AFF; color: #466AFF;
} &:hover{
a:hover{ opacity:0.85;
opacity:0.85; }
} }
} }
.noticeCont-back{ .noticeCont-back{
&:hover{ &:hover{
background: #F3F4F6; background: #F3F4F6;
cursor: pointer;
} }
} }
@ -203,7 +224,6 @@
} }
.noticeCont-text{ .noticeCont-text{
flex: auto; flex: auto;
position: relative; position: relative;
max-height: 48px; max-height: 48px;
@ -221,4 +241,8 @@
margin-right: 12px; margin-right: 12px;
} }
} }
}
.text-center{
text-align: center;
} }

View File

@ -0,0 +1,46 @@
import fetch from './fetch';
// 获取消息列表
export function noticePages(params) {
return fetch({
url: '/api/notice/noticePages',
method: 'get',
params
});
}
// 获取单个notice
export function getNotice(params) {
return fetch({
url: '/api/notice/getNotice',
method: 'get',
params
});
}
//新增notice
export function addNotice(data) {
return fetch({
url: '/api/notice/createNotice',
method: 'post',
data: data
});
}
//更新notice
export function updateNotice(data) {
return fetch({
url: '/api/notice/updateNotice',
method: 'PUT',
data: data
});
}
//删除notice
export function deleteNotice(data) {
return fetch({
url: '/api/notice/deleteNotice',
method: 'DELETE',
data,
});
}

View File

@ -0,0 +1,10 @@
import javaFetch from '../../javaFetch';
const developUrl = "https://test-search.trustie.net";
const testUrl = "https://test-search.trustie.net";
const productUrl = "https://wiki-api.trustie.net";
const { service, actionUrl } = javaFetch(productUrl, testUrl, developUrl);
export const httpUrl = actionUrl;
export default service;

105
src/forge/javaFetch.js Normal file
View File

@ -0,0 +1,105 @@
import { notification ,message} from 'antd';
import axios from 'axios';
import cookie from 'react-cookies';
import Login from './Wiki/components/Login';
export const TokenKey = 'autologin_trustie';
export default function javaFetch(productUrl,testUrl,developUrl ){
let actionUrl='';
if (window.location.href.indexOf('localhost') > -1) {
actionUrl = developUrl;
} else if(window.location.href.indexOf('testforgeplus')>-1){
actionUrl = testUrl;
axios.defaults.withCredentials = true;
}else if (window.location.href.indexOf('forgeplus') > -1) {
actionUrl = productUrl;
axios.defaults.withCredentials = true;
}
// 创建axios实例
const service = axios.create({
baseURL: actionUrl,
timeout: 10000, // 请求超时时间
});
// request拦截器
service.interceptors.request.use(config => {
if (cookie.load(TokenKey)) {
console.log(cookie.load(TokenKey));
config.headers['Authorization'] = cookie.load(TokenKey); // 让每个请求携带自定义token 请根据实际情况自行修改
}
if (window.location.port === "3007") {
// 模拟token为登录用户
const token = sessionStorage.token;
if (config.url.indexOf('?') === -1) {
config.url = `${config.url}?token=${token}`;
} else {
config.url = `${config.url}&token=${token}`;
}
}
return config;
}, error => {
console.log(error); // for debug
// Promise.reject(error);
});
// respone拦截器
service.interceptors.response.use(
response => {
const res = response||{};
if (res.status === 400) {
message.error(res.data.message || '操作失败');
return Promise.reject('error');
}
if (res.status === 401) {
message.error(res.data.message || '登录信息已过期');
return Promise.reject('error');
}
if (res.status === 403) {
message.error(res.data.message || '无权限!');
return Promise.reject('error');
}
if (res.status === 40001) {
notification.open({
message: "提示",
description: '账户或密码错误!',
});
return Promise.reject('error');
}
if (response.status !== 200 && res.status !== 200) {
notification.open({
message: "提示",
description: res.message,
});
} else {
return response.data;
}
},
error => {
console.log(error);
let res = error.response||{};
if (res.status === 400) {
message.error(res.data.message || '操作失败');
return Promise.reject('error');
}
if (res.status === 401) {
message.error(res.data.message || '登录信息已过期');
Login();
return Promise.reject('error');
}
if (res.status === 403) {
message.error(res.data.message || '无权限!');
return Promise.reject('error');
}
notification.open({
message: "提示",
description: error.message,
});
return Promise.reject(error);
}
);
console.log(service);
console.log(typeof service);
return {service,actionUrl};
}