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

View File

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

View File

@ -129,19 +129,26 @@
}
}
//popover小尖尖
.ant-popover-arrow{
// 右上角小铃铛单独样式
.notice-popover{
//popover小尖尖
.ant-popover-arrow{
display: none;
}
}
//popover框
.ant-popover-inner-content {
//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;
}
}
.messageHoverDiv .ant-menu-item{
margin-right: 24px !important;
}
.hoverNotice-head{
@ -164,23 +171,37 @@
font-weight: 400;
text-shadow: 0.5px 0 0 #333;
}
.none_panels{
height: 100%;
}
}
.message-icon{
position: relative;
.ant-scroll-number{
right:12px;
}
}
.hoverNotice-buttom{
display: flex;
justify-content: space-between;
padding: 12px 18px;
a{
color: #466AFF;
}
a:hover{
&:hover{
opacity:0.85;
}
}
}
.noticeCont-back{
&:hover{
background: #F3F4F6;
cursor: pointer;
}
}
@ -203,7 +224,6 @@
}
.noticeCont-text{
flex: auto;
position: relative;
max-height: 48px;
@ -222,3 +242,7 @@
}
}
}
.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};
}