Merge pull request '新增通知系统' (#84) from tongChong/forgeplus-react:feature_notification into pre_develop_dev

This commit is contained in:
jasder 2021-09-28 16:01:51 +08:00
commit 8818bafad2
29 changed files with 2221 additions and 90 deletions

64
package-lock.json generated
View File

@ -3875,6 +3875,52 @@
"warning": "^4.0.3"
}
},
"cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"requires": {
"cross-spawn": "^7.0.1"
},
"dependencies": {
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"requires": {
"isexe": "^2.0.0"
}
}
}
},
"cross-fetch": {
"version": "3.1.4",
"resolved": "https://registry.nlark.com/cross-fetch/download/cross-fetch-3.1.4.tgz",
@ -7240,8 +7286,7 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"optional": true
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"aproba": {
"version": "1.2.0",
@ -7658,8 +7703,7 @@
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"optional": true
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
@ -7715,7 +7759,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -7759,14 +7802,12 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"optional": true
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"optional": true
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
}
}
},
@ -16586,6 +16627,11 @@
}
}
},
"save-dev": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/save-dev/-/save-dev-0.0.1-security.tgz",
"integrity": "sha512-k6knZTDNK8PKKbIqnvxiOveJinuw2LcQjqDoaorZWP9M5AR2EPsnpDeSbeoZZ0pHr5ze1uoaKdK8NBGQrJ34Uw=="
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",

View File

@ -26,6 +26,7 @@
"code-prettify": "^0.1.0",
"codemirror": "^5.53.0",
"connected-react-router": "4.4.1",
"cross-env": "^7.0.3",
"css-loader": "^3.5.2",
"dompurify": "^2.0.15",
"dotenv": "4.0.0",
@ -103,6 +104,7 @@
"redux-thunk": "2.3.0",
"rsuite": "^4.3.4",
"sass-loader": "7.3.1",
"save-dev": "0.0.1-security",
"scroll-into-view": "^1.14.2",
"showdown": "^1.9.1",
"showdown-katex": "^0.8.0",
@ -122,7 +124,7 @@
},
"scripts": {
"start": "node --max_old_space_size=15360 scripts/start.js",
"build": "NODE_ENV=production node --max_old_space_size=15360 scripts/build.js",
"build": "cross-env NODE_ENV=production node --max_old_space_size=15360 scripts/build.js",
"test-build": "NODE_ENV=testBuild node --max_old_space_size=15360 scripts/build.js",
"pre-build": "NODE_ENV=preBuild node --max_old_space_size=15360 scripts/build.js",
"gen_stats": "NODE_ENV=production webpack --profile --config=./config/webpack.config.prod.js --json > stats.json",

View File

@ -0,0 +1,125 @@
/* eslint-disable react/jsx-no-duplicate-props */
import React, { useState } from 'react';
import * as ReactDOM from 'react-dom';
import { Modal, Button } from 'antd';
import './index.scss';
//
InitModal.defaultProps = {
okText: '确认', //
cancelText: '取消', //
className: '', //
inputId: 'copyText', //ID
onCancel:()=>{}, //
onOk:()=>{}, //
title:'提示', //
contentTitle:'', //
content:'', //
afterClose:()=>{}, //
};
// 使
export default function DelModal(props) {
renderModal({ ...props, type: 'delete' })
}
// 使
export function Confirm(props) {
renderModal({ ...props, type: 'confirm' })
}
function renderModal(props) {
const { type, afterClose } = props;
const div = document.createElement('div');
document.body.appendChild(div);
function destroy() {
afterClose && afterClose();
const unmountResult = ReactDOM.unmountComponentAtNode(div);
if (unmountResult && div.parentNode) {
div.parentNode.removeChild(div);
}
}
function modalType(type) {
if (type === 'delete') {
return <InitModal
title="删除"
contentTitle="确定要删除吗?"
okText="确认删除"
{...props}
afterClose={destroy}
contentTitle={<React.Fragment>
<i className="red-circle iconfont icon-shanchu_tc_icon mr3"></i>
{props.contentTitle}
</React.Fragment>}
/>
} else if (type === 'confirm') {
return <InitModal title="选择" afterClose={destroy} {...props} />
} else {
return <InitModal title="选择" afterClose={destroy} {...props} />
}
}
function render() {
setTimeout(() => {
ReactDOM.render(
modalType(type),
div,
);
});
}
render();
}
//
function InitModal({
onCancel,
onOk,
title,
contentTitle,
content,
okText,
cancelText,
afterClose,
className,
}) {
const [visible, setVisible] = useState(true);
function onCancelModal() {
setVisible(false);
onCancel && onCancel()
}
function onSuccess() {
setVisible(false);
onOk && onOk();
}
return (
<Modal
visible={visible}
onCancel={onCancelModal}
afterClose={afterClose}
title={title}
className={`myself-modal ${className}`}
centered
footer={[
<Button type="default" key="back" onClick={onCancelModal}>
{cancelText}
</Button>,
<Button className="foot-submit" key="submit" onClick={onSuccess}>
{okText}
</Button>,
]}
>
<div>
{contentTitle && <p className="content-title">{contentTitle}</p>}
<p className="content-descibe">{content}</p>
</div>
</Modal>
)
}

View File

@ -0,0 +1,63 @@
.myself-modal {
.ant-modal-header {
padding: 9px 24px;
background: #f8f8f8;
border-bottom: 1px solid #eee;
}
.ant-modal-title {
text-align: left;
}
.ant-modal-close {
top: 0px !important;
}
.ant-modal-close-x {
font-size: 24px;
}
.ant-modal-body {
text-align: center;
}
.content-title {
display: flex;
justify-content: center;
align-items: center;
margin: 2rem 0 1rem !important;
font-size: 16px;
color: #333;
letter-spacing: 0;
line-height: 29px;
font-weight: 400;
}
.red-circle {
align-self: flex-start;
color: #ca0002;
font-size: 1.5rem !important;
}
.content-descibe {
font-size: 14px;
color: #666;
line-height: 33px;
font-weight: 400;
}
.ant-modal-footer {
padding: 2rem 0;
text-align: center;
border: 0;
.ant-btn {
width: 6rem;
}
}
.foot-submit {
margin-left: 3rem;
color: #df0002;
&:hover {
border-color: #df0002;
}
}
.ant-btn-default:hover,
.ant-btn-default:active,
.ant-btn-default:focus {
background: #f3f4f6;
color: #333;
border-color: #d0d0d0;
}
}

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 - 200)) {
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

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import AccountProfile from "../../modules/user/AccountProfile";
import { getImageUrl } from 'educoder'
import axios from 'axios';
import { Input , notification , Dropdown , Menu } from 'antd';
import { Input , notification , Dropdown ,Popover, Menu,Badge, Button } from 'antd';
import { Link } from 'react-router-dom';
import LoginDialog from '../../modules/login/LoginDialog';
@ -13,6 +13,7 @@ import '../../modules/tpm/TPMIndex.css';
import CheckProfile from '../Component/ProfileModal/Profile';
import './header.scss';
import NoticeContent from './NoticeContent';
const $ = window.$
// TODO 这部分脚本从公共脚本中直接调用
const { Search } = Input;
@ -47,6 +48,7 @@ class NewHeader extends Component {
settings: null,
visiblemyss: false,
openSearch:false,
visible:false, //浮动消息框展示控制
}
}
componentDidMount() {
@ -92,9 +94,6 @@ class NewHeader extends Component {
this.setState({
user: newProps.user
})
if (newProps.Headertop !== undefined) {
old_url = newProps.Headertop.old_url
}
}
educoderlogin = () => {
@ -255,6 +254,7 @@ class NewHeader extends Component {
</div>
)
}
renderMenu=(personal)=>{
const { current_user } = this.props;
return(
@ -275,8 +275,12 @@ class NewHeader extends Component {
)
}
handleVisibleChange = visible => {
this.setState({ visible });
};
render() {
const { match} = this.props;
const { match ,resetUserInfo ,showNotification} = this.props;
let current_user = this.props.user;
let {
AccountProfiletype,
@ -285,6 +289,7 @@ class NewHeader extends Component {
headtypesonClickbool,
headtypess,
settings,
visible,
} = this.state;
/*用户名称 用户头像url*/
let activeIndex = false;
@ -395,7 +400,7 @@ class NewHeader extends Component {
{
settings.navbar && settings.navbar.map((item, key) => {
var new_link = item.link;
var user_login = this.props.user && this.props.user.login;
var user_login = current_user && current_user.login;
var is_hidden = item.hidden
if (new_link && (new_link.indexOf("courses") > -1 || new_link.indexOf("contests") > -1)) {
if (user_login) {
@ -426,25 +431,30 @@ 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)?
<Dropdown overlay={this.addMenu(settings && settings.add)} placement="bottomRight">
<i className="iconfont icon-tianjiafangda color-grey-6 ml30"></i>
<i className="iconfont icon-tianjiafangda color-grey-6 ml30 mr15"></i>
</Dropdown>:""
}
{this.props.user && this.props.user.login && notice_url ?
<div className="ml30 edu-menu-panel">
{user && user.login &&
<a href={`${notice_url}`} style={{ position: 'relative' }}>
<i className="iconfont icon-xiaoxilingdang color-grey-6"></i>
<span className="newslight" style={{ display: this.props.Headertop === undefined ? "none" : this.props.Headertop.new_message === true ? "block" : "none" }}>
</span>
</a>
}
</div>:""
{current_user && current_user.login ?
<Popover
overlayClassName="notice-popover"
placement={`bottomRight`}
content={<NoticeContent visible={visible} current_user={current_user} showNotification={showNotification} resetUserInfo={resetUserInfo}/>}
visible={visible}
onVisibleChange={this.handleVisibleChange}
destroyTooltipOnHide
>
<Link to={"/settings/notice"} className="message-icon">
<Badge count={current_user.message_unread_total}>
<i className="iconfont icon-xiaoxilingdang color-grey-6 ml15 mr15"></i>
</Badge>
</Link>
</Popover>
: ""
}
</div>
{!user || (user && !user.login) ?

View File

@ -0,0 +1,262 @@
import React, { useEffect, useState } from 'react';
import { Badge, Menu } from 'antd';
import { Link } from 'react-router-dom';
import axios from 'axios';
import AppPullRefresh from './AppPullRefresh';
import { noticeSourceType } from '../common/static';
import './header.scss';
import '../SecuritySetting/notice/manager/Index.scss';
import '../SecuritySetting/Index.scss';
import '../SecuritySetting/notice/myNotice/Index.scss';
function NoticeContent({ visible, showNotification, resetUserInfo, 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([]);//@
useEffect(() => {
resetUserInfo();
}, [noticeUnreadCount,atUnreadCount]);
useEffect(()=>{
setNoticePage(0);
setAtPage(0);
},[visible])
useEffect(() => {
const params = {
type: noticeType,
limit: 10,
page: noticeType === "notification" ? noticePage : noticeType === "atme" ? atPage : "",
status: 1,
}
getMessageList(params);
}, [noticePage, atPage]);
useEffect(() => {
const params = {
type: noticeType,
limit: 10,
page: 0,
status: 1,
};
if (initialize) {
params.type = "atme"
}
visible && getMessageList(params);
}, [visible]);
function getMessageList(params) {
axios.get(`/users/${login}/messages.json`, {
params: params,
}).then((response) => {
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);
}
}
})
}
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) {
changeReadMarkAll(noticeType);
} else {
showNotification(data.message);
}
});
}
function changeReadMarkAll(noticeType) {
if (noticeType === "notification") {
let list = noticeUnreadList.slice();
list.forEach(item => {
item.status = 2;
})
setNoticeUnreadList(list);
setNoticeUnreadCount(0);
} else if (noticeType === "atme") {
let list = atUnreadList.slice();
list.forEach(item => {
item.status = 2;
})
setAtUnreadList(list);
setAtUnreadCount(0);
}
}
// 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);
if (noticeUnreadCount > 0) {
setNoticeUnreadCount(noticeUnreadCount - 1);
}
} else if (item.type === "atme") {
let list = atUnreadList.slice();
let index = atUnreadList.indexOf(item);
list[index].status = 2;
setAtUnreadList(list);
if (atUnreadCount > 0) {
setAtUnreadCount(atUnreadCount - 1);
}
}
}
return (
<div className="messageHoverDiv notice01">
<div className="sshHead hoverNotice-head">
<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>
{/* 系统通知 */}
{noticeType === "notification" && <AppPullRefresh
className='hoverNotice-body' // className
onPullRefresh={() => { setNoticePage(noticePage + 1); }} //ajaxfunction
// type={2} //
count={noticeUnreadList.length} //
pageSize={10} //
>
{
noticeUnreadList.map(item => {
return (
<div key={item.id + Math.random()} className="noticeCont-back" onClick={() => { readItem(item) }}>
<div className={`noticeCont ${item.notification_url?'pointer':''}`}>
<span style={{ visibility: item.status === 1 ? 'visible' : 'hidden' }}>
<Badge color="#FA2020" />
</span>
<i className={"iconfont " + noticeSourceType[item.source]}></i>
<div className="noticeCont-text">
<span className="content-span notice-cont-span" dangerouslySetInnerHTML={{ __html: 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={atUnreadList.length} //
pageSize={10} //
>
{atUnreadList.map(item => {
return (
<div key={item.id + Math.random()} className="noticeCont-back" onClick={() => { readItem(item) }}>
<div className="noticeCont">
<span style={{ visibility: item.status === 1 ? 'visible' : 'hidden' }}>
<Badge color="#FA2020" />
</span>
<div className="noticeCont-text">
<span className="content-span atme-cont-span" dangerouslySetInnerHTML={{ __html: "<b>" + (item.sender ? item.sender.name : '') + "</b>&nbsp;&nbsp;&nbsp;" + 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" />
<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>
</div>
</div>
</div>
)
}) : "暂无数据" : ""} */}
<div className="hoverNotice-buttom">
<Link to={{pathname:"/settings/notice",query:{noticeType:noticeType}}}>全部消息</Link>
{noticeUnreadCount > 0 && noticeType === "notification" && <a onClick={readAll}>所有系统消息一键已读</a>}
{atUnreadCount > 0 && noticeType === "atme" && <a onClick={readAll}>所有@我一键已读</a>}
</div>
</div>
)
}
export default NoticeContent;

View File

@ -24,8 +24,9 @@
width: 34px;
height: 34px;
border-radius: 50%;
margin-left: 30px;
margin-left: 15px;
}
.currentMenu{
width: 120px;
text-align: center;
@ -127,3 +128,138 @@
text-align: right;
}
}
// 右上角小铃铛单独样式
.notice-popover{
//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;
}
}
.messageHoverDiv .ant-menu-item{
margin-right: 24px !important;
}
.hoverNotice-head{
margin-left: 18px;
& .ant-badge{
font-size: 14px !important;
}
&>.ant-menu-horizontal {
border-bottom: 1px solid #e8e8e8 !important;
}
}
.hoverNotice-body{
height: 342px;
overflow-y: scroll;
& b{
font-weight: 400;
text-shadow: 0.5px 0 0 #333;
}
.none_panels{
height: 100%;
}
}
.message-icon{
position: relative;
.ant-scroll-number{
right:12px;
padding: 0 0px;
}
}
.hoverNotice-buttom{
display: flex;
justify-content: space-between;
padding: 12px 18px;
a{
color: #466AFF;
&:hover{
opacity:0.85;
}
}
}
.noticeCont-back{
.pointer{
cursor: pointer;
}
&:hover{
background: #F3F4F6;
}
}
.noticeCont{
display: flex;
margin: 0 16px 0 18px;
padding: 12px 0 10px 0;
line-height: 24px;
border-bottom: 1px solid #EEEEEE;
cursor: default;
i{
font-size: 14px !important;
margin-right: 6px;
color: #333333;
}
.boldSpan{
font-weight: 400;
text-shadow: 0.5px 0 0 #333;
}
.noticeCont-text{
display: flex;
color:#333333;
flex:auto;
justify-content: space-between;
& .content-span{
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
& .atme-cont-span{
width: 272px;
}
& .notice-cont-span{
width: 255px;
}
.timeSpan{
font-size: 12px;
color: #666666;
}
.at-name{
margin-right: 12px;
}
}
}
.text-center{
text-align: center;
}

View File

@ -1,7 +1,7 @@
import React , { Component } from 'react';
import nodata from './Images/nodata.png';
import './css/index.scss';
class Nodata extends Component{
render(){
const { _html , small } = this.props;

View File

@ -10,10 +10,6 @@ const Apply = Loadable({
loader: () => import("./Apply"),
loading: Loading,
});
const Notify = Loadable({
loader: () => import("./Notify"),
loading: Loading,
});
const UndoEvent = Loadable({
loader: () => import("./UndoEvent"),
loading: Loading,
@ -23,7 +19,7 @@ function Index(props){
const pathname = props.history.location.pathname;
const user = props.user;
const [ menu , setMenu ] = useState("notify");
const [ menu , setMenu ] = useState("undo");
const [ messagesCount , setMessagesCount ] = useState(0);
const [ transferCount , setTransferCount ] = useState(0);
const [ applyCount , setApplyCount ] = useState(0);
@ -48,10 +44,6 @@ function Index(props){
useEffect(()=>{
if(pathname && username){
if(pathname === `/${username}/notice`){
setMenu("notify");
changeNum(user.undo_messages);
}
if(pathname === `/${username}/notice/undo`){
setMenu("undo");
}
if(pathname === `/${username}/notice/apply`){
@ -83,14 +75,8 @@ function Index(props){
return (
<div>
<ul className="noticeMenu">
<li className={menu === "notify" ? "active":""}>
<Link to={`/${username}/notice`} onClick={changeNum}>
<span>通知</span>
{messagesCount ? <span className="unNum">{messagesCount}</span>:""}
</Link>
</li>
<li className={menu === "undo" ? "active":""}>
<Link to={`/${username}/notice/undo`}>
<Link to={`/${username}/notice`}>
<span>接收仓库</span>
{transferCount ? <span className="unNum">{transferCount}</span>:""}
</Link>
@ -109,16 +95,10 @@ function Index(props){
return <Apply {...props} {...p} deleteEvent={deleteEvent}/>;
}}
></Route>
<Route
path="/:username/notice/undo"
render={(p) => {
return <UndoEvent {...props} {...p} deleteEvent={deleteEvent}/>;
}}
></Route>
<Route
path="/:username/notice"
render={(p) => {
return <Notify {...props} {...p} deleteEvent={deleteEvent}/>;
return <UndoEvent {...props} {...p} deleteEvent={deleteEvent}/>;
}}
></Route>
</Switch>

View File

@ -14,6 +14,16 @@ import { Link } from 'react-router-dom';
import './Index.scss';
const MyNoticeIndex = Loadable({
loader: () => import("./notice/myNotice/Index"),
loading: Loading,
});
const NoticeManager = Loadable({
loader: () => import("./notice/manager/Index"),
loading: Loading,
});
const SSHNew = Loadable({
loader: () => import("./sub/New"),
loading: Loading,
@ -26,6 +36,12 @@ const SSHIndex = Loadable({
loader: () => import("./sub/SSH"),
loading: Loading,
});
const PrivateLetter = Loadable({
loader: () => import("./notice/privateLetter/Index"),
loading: Loading,
});
function Index(props){
const { current_user } = props;
const { pathname } = props.location;
@ -39,18 +55,35 @@ function Index(props){
<img src={getImageUrl(`/${current_user && current_user.image_url}`)} alt=""/>
<span>{current_user && current_user.username}</span>
</div>
<ul className="securityUl">
<ul className="securityUl ul-border-buttom">
<li>个人信息</li>
<li className={pathname.indexOf("/settings/profile")>-1 ?"active":""}><Link to={`/settings/profile`}><i className="iconfont icon-gerenziliao mr5 font-14"></i>基本资料</Link></li>
<li className={pathname.indexOf("/settings/profile")>-1 ?"active":""}><Link to={`/settings/profile`}><i className="iconfont icon-gerenziliao mr5 font-14"></i><span className="text-shodow-bold">基本资料</span></Link></li>
</ul>
<ul className="securityUl ul-border-buttom">
<li>消息通知</li>
<li className={(pathname.indexOf("/settings/notice")>-1 && pathname.indexOf("/settings/notice/config") == -1) || pathname.indexOf("/settings/notice/privateLetter")>-1 ?"active":""}><Link to={"/settings/notice"}><i className="iconfont icon-wodetongzhi"></i><span className="text-shodow-bold">我的通知</span></Link></li>
{/* <li className={pathname.indexOf("/settings/notice/config")>-1 ?"active":""}><Link to={'/settings/notice/config'}><i className="iconfont icon-tongzhiguanli"></i><span className="text-shodow-bold">通知管理</span></Link></li> */}
</ul>
<ul className="securityUl">
<li>安全设置</li>
<li className={pathname.indexOf("/settings/SSH")>-1 ?"active":""}><Link to={`/settings/SSH`}><i className="iconfont icon-xuanzhongssh_icon mr5 font-14"></i>SSH密钥</Link></li>
<li className={pathname.indexOf("/settings/SSH")>-1 ?"active":""}><Link to={`/settings/SSH`}><i className="iconfont icon-xuanzhongssh_icon mr5 font-14"></i><span className="text-shodow-bold">SSH密钥</span></Link></li>
</ul>
</div>
<LongWidth>
<Gap>
<Switch>
<Route
path="/settings/notice"
render={(p) => (
<MyNoticeIndex {...props} {...p}/>
)}
></Route>
<Route
path="/settings/notice/config"
render={(p) => (
<NoticeManager {...props} {...p}/>
)}
></Route>
<Route
path="/settings/SSH/new"
render={(p) => (
@ -69,12 +102,17 @@ function Index(props){
<SSHIndex {...props} {...p}/>
)}
></Route>
<Route
path="/settings/notice/privateLetter"
render={(p)=>(
<PrivateLetter{...props} {...p}/>
)}
></Route>
</Switch>
</Gap>
</LongWidth>
</Box>
</div>
</div>
)
}

View File

@ -8,42 +8,44 @@
width: 198px;
border: 1px solid rgba(153, 153, 153, 0.22);
border-radius: 4px;
min-height: 400px;
// min-height: 400px;
margin-bottom: 30px;
.userDetail{
background: rgba(153, 153, 153, 0.05);
border-radius: 4px 4px 0px 0px;
padding:20px 25px;
display: flex;
align-items: center;
justify-content: flex-start;
text-align: center;
height: 105px;
img{
height: 48px;
width: 48px;
border-radius: 50%;
margin-right: 12px;
}
span{
font-size: 16px;
color: #333;
max-width: 90px;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: 500;
}
}
.securityUl{
padding:20px 16px;
padding-left: 17px;
color: #333;
margin-bottom: 0px;
padding-bottom: 0px;
padding-bottom: 12px;
padding-top: 5px;
li{
font-size: 14px;
margin-top: 10px;
margin-bottom: 10px;
height: 27px;
line-height: 27px;
position: relative;
cursor: pointer;
cursor: default;
a{
color: #666;
&:hover{
@ -52,32 +54,57 @@
}
&.active a{
color: #333;
.text-shodow-bold{
font-weight: 400;
text-shadow: 0.5px 0 #333;
}
&:first-child{
font-size: 16px;
}
&.active::before{
position: absolute;
left: -16px;
top:0px;
height: 100%;
width: 2px;
left: -18px;
top:6px;
height: 15px;
width: 3px;
border-radius: 2px;
content: "";
background-color: #2A61FF;
background-color: #466AFF;
}
i{
font-size: 14px !important;
margin-right: 8px;
}
}
}
.ul-border-buttom{
border-bottom: 1px solid rgba(153, 153, 153, 0.22);
padding-bottom: 5px;
}
}
.sshHead{
display: flex;
align-items: center;
padding:15px 20px;
padding:6px 0px;
margin-bottom: 15px;
justify-content: space-between;
border-bottom: 1px solid #EEEEEE;
&>span{
&>.text-shadow07{
font-size: 18px;
font-weight: 400;
color: #333333;
text-shadow: 0.5px 0 #333;
}
&>.add-SSH-title{
font-size: 16px;
font-weight: 400;
color: #333333;
text-shadow: 0.5px 0 #333;
}
& .but25{
padding:0 12px;
}
}
.ant-list-item{
padding:20px;
border-bottom: 1px solid #eee!important;
@ -109,7 +136,7 @@
.questionLink{
padding:15px 20px;
a{
color: #4B7AFF;
color: #466AFF;
&:hover{
text-decoration: underline;
}
@ -148,3 +175,22 @@
}
}
}
.but25{
margin-bottom: 5px;
background-color: #466AFF;
color: #fff;
border-color: #466AFF;
&:hover{
opacity: 0.8;
background-color: #466AFF;
border-color: #466AFF;
}
&:active{
opacity: 1;
background-color: #466AFF;
border-color: #466AFF;
}
}
.blue-Purple{
color: #466AFF !important;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 1.0 KiB

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;

View File

@ -0,0 +1,100 @@
import { Button, Checkbox } from "antd";
import React from "react";
import './Index.scss';
function NoticeManager(props){
return(
<div className="notice01">
<div className="sshHead">
<span className="text-shadow07">通知管理</span>
</div>
<div>
<span className="notice-manager-tip">您可以通过通知管理来选择接受通知的方式</span>
<div className="manager-cont-top">
我创建或负责的
</div>
<div className="manager-cont">
<div className="manager-cont-title">易修状态变更</div>
<Checkbox defaultChecked='true' disabled>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">易修截止日期到达最后一天</div>
<Checkbox defaultChecked='true' disabled>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">合并请求状态变更</div>
<Checkbox defaultChecked='true' disabled>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">易修有新的评论</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">合并请求有新的评论</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont-top">
我管理的仓库
</div>
<div className="manager-cont">
<div className="manager-cont-title">被关注</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">被点赞</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">被复刻</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">有新的里程碑</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont-top">
我关注的仓库
</div>
<div className="manager-cont">
<div className="manager-cont-title">被删除</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">被转移</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">有新的易修</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">有新的合并请求</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
<div className="manager-cont">
<div className="manager-cont-title">有新的版本发布</div>
<Checkbox defaultChecked='true'>站内信</Checkbox>
<Checkbox >邮件</Checkbox>
</div>
</div>
</div>
)
}
export default NoticeManager;

View File

@ -0,0 +1,87 @@
.notice-manager-tip{
font-size:16px;
font-weight:400;
}
.manager-cont-top{
font-size: 14px;
font-weight: 600;
height: 44px;
padding-left: 20px;
background: #FAFCFF;
border: 1px solid #89a4f7;
line-height: 44px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
margin-top: 25px;
}
.manager-cont{
padding: 8px 20px 6px;
display: flex;
align-items: center;
font-size: 14px;
.manager-cont-title{
width: 320px;
}
}
.notice01{
.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner::after {
border-color: white;
}
.ant-checkbox-disabled .ant-checkbox-inner {
background-color: #999999 !important;
border-color: #999999 !important;
}
.ant-checkbox-checked .ant-checkbox-inner {
background-color: #466AFF;
border: #466AFF;
}
.ant-checkbox-checked::after{
border: 1px solid #466AFF;
}
.ant-checkbox-wrapper:hover .ant-checkbox-inner, .ant-checkbox:hover .ant-checkbox-inner, .ant-checkbox-input:focus + .ant-checkbox-inner,.ant-radio-checked .ant-radio-inner,.ant-radio-wrapper:hover .ant-radio, .ant-radio:hover .ant-radio-inner, .ant-radio-input:focus + .ant-radio-inner {
border-color: #466AFF;
}
.ant-checkbox + span,.manager-cont-title {
color: #000000;
}
.ant-radio-inner::after{
background-color:#466AFF;
}
.but25{
background-color: #466AFF;
color: #fff;
border-color: #466AFF;
&:hover{
opacity: 0.8;
background-color: #466AFF;
border-color: #466AFF;
}
&:active{
opacity: 1;
background-color: #466AFF;
border-color: #466AFF;
}
}
::-webkit-scrollbar {
width: 5px; /*对垂直流动条有效*/
}
/*定义滑块颜色、内阴影及圆角*/
::-webkit-scrollbar-thumb {
border-radius: 6px;
box-shadow: inset 0 0 6px #FFF;
background-color: #D4D4D4;
}
::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px #fff;
background-color: #FFF;
}
}

View File

@ -0,0 +1,263 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import { Badge, Button, Checkbox, Menu, Pagination } from 'antd';
import DelModal from '../../../Component/ModalFun';
import NoneData from '../../../Nodata.js';
import { noticeSourceType } from '../../../common/static';
import './Index.scss';
import '../manager/Index.scss'
function MyNotice(props) {
let current_user = props.current_user;
let resetUserInfo = props.resetUserInfo;
//tab
let popover = props.location.query && props.location.query.noticeType;
let pageSize = 15;
const [noticeType, setNoticeType] = useState(popover==="atme"?"2":"0");//tab
const [selectedNum, setSelectedNum] = useState(0);//@
const [isBatchDelete, setIsBatchDelete] = useState(false);//@
const [batchDeleteCheckedAll, setBatchDeleteCheckAll] = useState(false);//@--
const [noticeUnreadCount, setNoticeUnreadCount] = useState();//
// const [letterUnreadCount, setLetterUnreadCount] = useState(0);//
const [atUnreadCount, setAtUnreadCount] = useState();//@
const [messageList, setMessageList] = useState([]);
const [messTotalCount, setMessTotalCount] = useState();//
const [currentPage, setCurrentPage] = useState(1);//
const [onlyUnread, setOnlyUnread] = useState();
useEffect(()=>{
popover==="atme" ? setNoticeType("2"):setNoticeType("0");
},[popover])
useEffect(()=>{
resetUserInfo();
},[noticeUnreadCount,atUnreadCount])
useEffect(() => {
getMessageList();
}, [noticeType, onlyUnread, currentPage, current_user])
function getMessageList() {
const params = {
type: noticeType === "0" ? "notification" : noticeType === "2" ? "atme" : "",
status: onlyUnread ? onlyUnread : "",
limit: pageSize,
page: currentPage,
};
axios.get(`/users/${current_user.login}/messages.json`, {
params: params,
}).then((response) => {
if(response && response.data){
setNoticeUnreadCount(response.data.unread_notification);
setAtUnreadCount(response.data.unread_atme);
setMessageList(response.data.messages);
setMessTotalCount(response.data.total_count);
}
});
}
function readNotice(id){
if(id){
const params = {
type: noticeType === "0" ? "notification" : noticeType === "2" ? "atme" : "",
ids:id,
};
axios.post(`/users/${current_user.login}/messages/read.json`,params).then((response)=>{
if(response.status === 200){
getMessageList();
//
let totlaPage = Math.ceil((messTotalCount-1)/pageSize);
setCurrentPage(currentPage>=totlaPage? totlaPage : currentPage);
}
});
}
}
function handleClick(e) {
setNoticeType(e.key);
setCurrentPage(1);
setOnlyUnread();
if (e.key != "2") {
setIsBatchDelete(false);
}
}
function onChange(e) {
var checkboxNum = 0;
let messageListNew=messageList.slice();
messageListNew.map((item)=>{
if(item.id===e.target.value){
item.checkedBatch = e.target.checked;
}
item.checkedBatch?checkboxNum++:"";
});
setMessageList(messageListNew);
setSelectedNum(checkboxNum);
setBatchDeleteCheckAll(checkboxNum === messageList.length);
}
function onChangeAll(e) {
setBatchDeleteCheckAll(e.target.checked);
setSelectedNum(e.target.checked?messageList.length:0);
let messageListNew=messageList.slice();
messageListNew.map((item)=>{
item.checkedBatch = e.target.checked;
});
setMessageList(messageListNew);
}
function deleteNotice(id) {
const ids = [];
if(!id){
messageList.map(item=>{
item.checkedBatch && ids.push(item.id);
});
}
DelModal({
title: noticeType === "1" ? '删除私信用户' : id ? '删除消息' : '批量删除',
contentTitle: noticeType === "1" ? '您确定要删除与 xxx 的聊天吗?' : id ? '您确定要删除这条@我消息吗?' : '您确定要删除选中的' + selectedNum + '条消息吗?',
content: noticeType === "1" ? '此操作将删除与xxx的聊天框和xxx的所有聊天记录请进行确认以防数据的丢失' : id ? '此操作将删除这条消息,请进行确认以防数据的丢失' : '此操作将删除选中的' + selectedNum + '条消息,请进行确认以防数据的丢失',
onOk: () => {
const params = {
type: noticeType === "0" ? "notification" : noticeType === "2" ? "atme" : "",
ids:id?id:ids,
};
axios.delete(`/users/${current_user.login}/messages.json`,{
data:params,
}).then((response)=>{
if(response.status === 200){
getMessageList();
//
let totlaPage = Math.ceil((messTotalCount-1)/pageSize);
setCurrentPage(currentPage>=totlaPage? totlaPage : currentPage);
setSelectedNum(0);
setBatchDeleteCheckAll(false);
}
});
}
});
}
function cancelBatchDelete(){
setIsBatchDelete(false);
setSelectedNum(0);
//
let messageListNew=messageList.slice();
messageListNew.map((item)=>{
item.checkedBatch = false;
});
setMessageList(messageListNew);
setBatchDeleteCheckAll(false);
}
//
function turnToMess(item){
if(item.notification_url){
window.open(`${item.notification_url}`);
readNotice([item.id]);
}
}
return (
<div className="notice01">
<div className="sshHead">
<Menu mode="horizontal" selectedKeys={noticeType} onClick={handleClick}>
<Menu.Item key="0"><Badge count={noticeUnreadCount} title="">系统通知</Badge></Menu.Item>
{/* <Menu.Item key="1" id="item-private"><Badge count={0}>私信</Badge></Menu.Item> */}
<Menu.Item key="2"><Badge count={atUnreadCount}>@</Badge></Menu.Item>
</Menu>
{(noticeType==="0" && noticeUnreadCount>0) || (noticeType==="2"&& atUnreadCount>0) ? <button className="but25" onClick={()=>{readNotice([-1])}}>所有{noticeType === "0" ? "系统通知" : noticeType === "1" ? "私信" : "@我"}一键已读</button>:""}
</div>
<div className={isBatchDelete ? "invisible " : "visible"}>
<div className="vertical-center-style">
{onlyUnread===1 || messageList && messageList.length>0 ? <Checkbox checked={onlyUnread} onChange={(e) => e.target.checked ? setOnlyUnread(1) : setOnlyUnread()}>仅看未读{noticeType === "1" ? `私信12` : noticeType === "0" ? `消息(${noticeUnreadCount}` : `消息(${atUnreadCount}`}</Checkbox>:""}
</div>
{noticeType === "2" && messageList && messageList.length > 0 ? <button className="batchDeleteBut" onClick={() => { setIsBatchDelete(true); }}>批量删除</button> : ""}
</div>
{messageList && messageList.length===0 ? <NoneData _html="暂无相关消息"/>:""}
{messageList && messageList.length>0 && <div className={isBatchDelete ? 'visible' : 'invisible'}>
<div className="vertical-center-style">
<Checkbox onChange={onChangeAll} checked={batchDeleteCheckedAll}>全选</Checkbox>
&nbsp;&nbsp;&nbsp;已选择&nbsp;<span className="numberSpan">{selectedNum}</span>&nbsp;
</div>
<div>
<button onClick={cancelBatchDelete}>取消</button>&nbsp;&nbsp;&nbsp;&nbsp;
<button className="deleteBut" onClick={selectedNum > 0 ? ()=>deleteNotice() : () => { }}>删除</button>
</div>
</div>}
{messageList && messageList.map(item => {
//
if (noticeType === "0") {
//
return (
<div className="mynotice-content vertical-center-style" key={item.id}>
<div className="mynotice-cont stretch-style">
{item.status === 1 ? <Badge color="#FA2020" /> : <span className="system-notice-blank"></span>}
<i className={"iconfont "+noticeSourceType[item.source]}></i>
<span className={`sysNotice-length ${item.notification_url?'highlightSpan':''}`} onClick={() => {turnToMess(item)}} dangerouslySetInnerHTML={{__html: item.content}}></span>
</div>
<div className="mynotice-cont vertical-center-style float-left-little">
<span className={item.status === 1?"timeSpan":""}>{item.time_ago}</span>
{item.status === 1 && <span className="invisable-read" onClick={()=>readNotice([item.id])}>标记为已读</span>}
</div>
</div>
)
} else if (noticeType === "2") {
//@
return (
<div className={`mynotice-content vertical-center-style ${isBatchDelete?'batchDel':''}`} key={item.id}>
<div className="mynotice-cont vertical-center-style">
<Checkbox value={item.id} className={isBatchDelete ? 'visible-checkbox' : 'invisible-checkbox'} onChange={onChange} checked={item.checkedBatch}></Checkbox>
{item.sender && <img src={`https://testforgeplus.trustie.net//${item.sender.image_url}`} className="currentImg" onClick={()=>{window.open(`/${item.sender && item.sender.login}`);}}/>}
<div className={`atme-notice-text stretch-style ${item.notification_url && 'highlightSpan'}`} onClick={() => {turnToMess(item)}}>
{item.status === 1 ? <Badge color="#FA2020"/> : <span className="system-notice-blank"></span>}
{item.sender && <span className="atme-length" dangerouslySetInnerHTML={{__html: "<b class = 'atme-notice-name'>" + item.sender.name+ "</b> "+ item.content +" 中@我"}}></span>}
</div>
</div>
<div className="mynotice-cont vertical-center-style">
<span className={!isBatchDelete && item.status === 1?"timeSpan":""}>{item.time_ago}</span>
{!isBatchDelete && item.status === 1 && <span className="invisable-read" onClick={()=>readNotice([item.id])}>标记为已读</span>}&nbsp;&nbsp;&nbsp;
{!isBatchDelete && <span className="invisable-read float-left-little" onClick={()=>deleteNotice([item.id])}>删除</span>}
</div>
</div>
)
} else{
//
{/* <div className="mynotice-content vertical-center-style">
<Badge count={95}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img" /></Badge>
<div className="private-letter-right">
<div>
<span>蒋宇航</span>
<span className="timeSpan">4分钟前</span>
<a onClick={deleteNotice}>删除</a>
</div>
<div onClick={() => props.history.push('/settings/notice/privateLetter')}>
<span className="highlightSpan letter-length-limit">最好的OpenStack控制台对标OpenStack社区Horizon项目,最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
</div>
</div>
</div> */}
}
})}
{/* 分页 */}
{!isBatchDelete && <div className="paging">
<Pagination
simple
current = {currentPage}
pageSize={pageSize}
onChange={(page)=>{setCurrentPage(page)}}
total = {messTotalCount}
hideOnSinglePage
></Pagination>
</div>}
</div>
)
}
export default MyNotice;

View File

@ -0,0 +1,259 @@
.whiteBack .boies .sshHead{
padding:0 10px 0px 0px;
}
.sshHead{
.ant-badge{
font-size: 16px;
color: #333333;
}
.ant-menu-item{
padding:0px;
margin-right:34px!important;
height: 34px;
width: 64px;
text-align: center;
line-height: 0px;
position: relative;
}
li.ant-menu-item, .ant-menu-horizontal > .ant-menu-item {
border-bottom: 0px;
}
& .ant-menu-item-selected{
color: #333333;
font-weight: 400;
text-shadow: 0.5px 0 #333;
border-bottom: 2px solid #2A61FF !important;
}
.ant-badge-count, .ant-badge-dot, .ant-badge .ant-scroll-number-custom-component {
right: -6px;
-webkit-box-shadow: 0 0 0 0;
box-shadow: 0 0 0 0;
}
.ant-badge-multiple-words {
padding: 0 0px;
}
.ant-menu-horizontal {
border-bottom: 0px solid #e8e8e8;
}
button{
padding:0 5px;
}
}
button {
color: #333333;
background: #FAFBFC;
border: 1px solid #D0D0D0;
border-radius: 4px;
height: 32px;
}
button:hover {
background: #F3F4F6;
}
button:active {
background: #EBECF0;
}
.deleteBut{
color: #DF0002;
}
.deleteBut:hover{
background: #DF0002;
border: 1px solid #DF0002;
color:#FFFFFF;
}
.deleteBut:active{
background: #CE0002;
border: 1px solid #CE0002;
color:#FFFFFF;
}
.mynotice-content {
justify-content: space-between;
padding: 15px 0 15px 10px;
border-bottom: 1px solid #EEEEEE;
color: #333333;
&:hover{
background: #F3F4F6;
}
& img{
cursor: pointer;
}
& b{
font-weight: 400;
text-shadow: 0.5px 0 #333;
}
& .invisable-read{
display: none;
}
&:hover .invisable-read{
display: block;
color: #466AFF;
opacity: 0.6;
cursor: pointer;
&:hover{
opacity: 1;
}
}
&:hover .timeSpan{
display: none;
}
i{
font-size: 16px !important;
margin-right: 5px;
}
.boldSpan{
font-weight: 400;
text-shadow: 0.5px 0 0 #333;
margin: 0 8px;
}
& .currentImg{
width: 40px;
height: 40px;
margin-left: 0px;
}
& .private-letter-img + .ant-badge-count{
top: 2px;
right: 5px;
height: 18px;
min-width: 18px;
line-height: 18px;
padding: 0 0;
}
& .highlightSpan:hover{
color: #466AFF;
cursor: pointer;
}
.mynotice-cont{
padding:0;
cursor: default;
& .visible-checkbox{
margin-right: 10px;
}
& .invisible-checkbox{
display: none;
}
.atme-notice-text{
margin-left: 6px;
& .atme-notice-name{
margin: 0 0 ;
}
& .atme-length{
max-width: 48rem;
word-break: break-all;
}
}
}
& .ant-badge-count, .ant-badge-dot, .ant-badge .ant-scroll-number-custom-component {
-webkit-box-shadow: 0 0 0 0;
box-shadow: 0 0 0 0;
top: 3px;
right: 4px;
min-width: 8px;
height: 8px;
}
& .system-notice-blank{
margin-right: 14px;
}
}
.batchDel{
& .currentImg, & .atme-notice-text{
pointer-events: none;
}
}
.baselineDiv{
align-items: baseline;
}
.invisible {
display: none;
}
.visible {
display: flex;
justify-content: space-between;
height: 30px;
padding: 0 10px;
color: #333333;
margin-bottom: 5px;
button{
padding:0px 12px;
}
.batchDeleteBut{
border:1px solid #466AFF;
color: #466AFF;
}
}
.private-letter-right {
flex: auto;
margin: 0px 10px 0 16px;
& div{
display: flex;
justify-content: space-between;
}
}
.letter-length-limit{
max-width: 50rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.sysNotice-length{
max-width: 52rem;
word-break: break-all;
}
.numberSpan{
color: #466AFF;
}
.vertical-center-style{
display: flex;
align-items: center;
}
.stretch-style{
display: flex;
align-items: baseline;
}
.float-left-little{
margin-right: 10px;
}
.float-right-little{
margin-left: 12px;
}
.paging{
text-align: center;
margin: 12px;
}

View File

@ -0,0 +1,200 @@
import React, { useState, useEffect } from 'react';
import './Index.scss'
import '../manager/Index.scss'
import { Button, Input, Icon, Badge } from 'antd';
import { Link } from 'react-router-dom';
function PrivateLetter(props){
const { TextArea,Search } = Input;
function deleteNotice(){
alert("删除消息");
}
return(
<div className="private-letter notice01">
<div className="pl-content">
<div className="pl-name vertical-center-style">
<Link to="/settings/notice/myNotice"><i className="iconfont icon-zuojiantou"></i></Link>
<span>蒋宇航</span>
</div>
<div className="plcontent-list">
<div className="vertical-center-style plclo">
<img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg"/>
<div className="message-bubble mb-other"></div>
<div className="notice-content vertical-center-style">
嗨在吗哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
<a className="pld01">删除</a>
</div>
<span>2021-08-29 11:59</span>
</div>
<div className="notice-my vertical-center-style plclo">
<img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg"/>
<div className="message-bubble"></div>
<div className="notice-content vertical-center-style">
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
<a className="pld01">删除</a>
</div>
<span>2021-08-29 11:59</span>
</div>
</div>
<div className="private-letter-present">
<div>
<img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg"/>
<TextArea className="private-letter-cont" rows="2" />
</div>
<div className="presentNotice">
<Button className="private-letter-cont-mt25 but25" type="primary">发送</Button>
</div>
</div>
</div>
<div className="pl-list">
<div className="list-sort vertical-center-style">
<Search
className=""
placeholder="搜索用户"
enterButton={<i className="iconfont icon-sousuo"></i>}
onSearch={value => console.log(value)}
style={{ width: 265 }}
/>
</div>
<div className="list-scroll">
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
<div className="list-scroll-content vertical-center-style">
<Badge count={5}><img src="https://testforgeplus.trustie.net//system/lets/letter_avatars/2/D/208_124_118/120.png" className="currentImg private-letter-img"/></Badge>
<div className="private-letter-list-content-right">
<div className="ls-cont vertical-center-style">
<span>蒋宇航</span>
<span className="private-letter-list-content-right-content-top-timeSpan">4分钟前</span>
</div>
<div className="ls-cont vertical-center-style">
<span className="ls-content-span">最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性页面性能等方面进行深度优化提供简单控制台</span>
<a onClick={deleteNotice}>删除</a>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default PrivateLetter;

View File

@ -0,0 +1,192 @@
.private-letter{
border-radius: 4px 4px 0px 0px;
border: 1px solid rgba(151, 151, 151, 0.24);
display: flex;
.currentImg{
width: 40px;
height: 40px;
margin-left: 0px;
}
& .private-letter-img + .ant-badge-count{
top: 2px;
right: 5px;
}
}
.pl-content{
flex: auto;
.pl-name{
height: 3rem;
border-bottom: 1px solid #EEEEEE;
&>a{
position: relative;
left: 26px;
color:#999999;
}
span{
font-size: 16px;
font-weight: 600;
color: #333333;
flex: auto;
text-align: center;
}
}
.plcontent-list{
height: 24rem;
padding:20px;
overflow-y: scroll;
.plclo{
margin-bottom: 30px;
}
.notice-my{
flex-direction: row-reverse;
& .pld01{
left: 0;
}
}
& .notice-content{
padding:8px 10px;
background: #F4F4F4;
border-radius:5px;
position: relative;
max-width: 20rem;
.pld01{
display: none;
}
&:hover .pld01{
display: block;
position: absolute;
right: 0;
bottom: -2em;
color: #999999;
font-size: 12px;
&:hover{
color: #666666;
}
}
}
& .message-bubble{
position:relative;
width:0;
height:0;
font-size:0;
border:solid 8px;
border-color:#FFFFFF #FFFFFF #FFFFFF #F4F4F4;
}
& .mb-other{
border-color:#FFFFFF #F4F4F4 #FFFFFF #FFFFFF;
}
& span{
margin: 0 10px;
font-size: 12px;
color: #999999;
line-height: 17px;
}
}
.private-letter-present{
padding:20px 20px;
& div{
display: flex;
align-items: center;
justify-content: flex-end;
flex: auto;
}
.private-letter-cont-mt25{
margin-top: 8px;
padding:0 22px;
}
.private-letter-cont, .private-letter-cont .ant-input-suffix{
background-color: #fafafa !important;
margin-left: 17px;
}
.private-letter-cont{
resize:none;
&:hover,&:focus{
background-color: #fafafa !important;
}
}
}
}
.pl-list{
width: 30%;
border-left: 1px solid #EEEEEE;
.list-sort{
justify-content: center;
height: 3rem;
border-bottom: 1px solid #EEEEEE;
& .ant-btn-primary{
width: 2.3rem;
color: #466AFF;
background: #eff2ff;
border: 1px solid rgba(151, 151, 151, 0.24);
}
& .ant-btn{
padding: 0;
}
// & .ant-input:hover{
// border: 1px solid red !important;
// }
}
.list-scroll{
height: 32.4rem;
overflow-y: scroll;
}
.list-scroll-content{
padding: 10px 10px 15px 15px;
border-bottom: 1px solid #EEEEEE;
// &:last-child{
// border-bottom: 0px;
// }
&:hover{
background: #F3F4F6;
}
& a{
display: none;
}
&:hover a{
display: block;
color: #466AFF;
}
}
.ls-cont{
width: 13rem;
// flex: auto;
margin-left: 15px;
justify-content: space-between;
& .ls-content-span{
max-width: 11rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.vertical-center-style{
display: flex;
align-items: center;
}
img{
width: 45x;
height: 45px;
}

View File

@ -34,7 +34,7 @@ function New({ form , showNotification , history }) {
return(
<div>
<div className="sshHead">
<span><Link to={`/settings/SSH`} className="color-blue">SSH密钥</Link><i className="iconfont icon-youjiantou ml5 mr5 font-12"></i>添加SSH密钥</span>
<span className="add-SSH-title"><Link to={`/settings/SSH`} className="blue-Purple">SSH密钥</Link><i className="iconfont icon-youjiantou ml5 mr5 font-12"></i>添加SSH密钥</span>
</div>
<Form className="sshForm">
<Form.Item label="标题" validateStatus={msg && msg.status === 10001 ? "error":undefined} help={msg && msg.status === 10001 ? msg.message:undefined}>
@ -51,7 +51,7 @@ function New({ form , showNotification , history }) {
<TextArea placeholder="支持以'ssh-rsa','ssh-dss','ssh-ed25519','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521'开头" autoSize={{ minRows: 6, maxRows: 6 }}/>
)}
</Form.Item>
<Button type="primary" style={{width:"100px"}} onClick={submit}>确定</Button>
<button style={{width:"100px"}} onClick={submit} className="but25">确定</button>
</Form>
</div>
)

View File

@ -55,8 +55,8 @@ function SSH(props) {
<DeleteBox visible={visible} onCancel={()=>setVisible(false)} onSuccess={onSuccess}/>
<SSHDetail visible={visibleDesc} onCancel={()=>setVisibleDesc(false)} desc={content}/>
<div className="sshHead">
<span>SSH密钥</span>
<Button type="primary" size="large" onClick={()=>props.history.push('/settings/SSH/new')}>添加SSH密钥</Button>
<span className="text-shadow07">SSH密钥</span>
<button type="primary" size="large" onClick={()=>props.history.push('/settings/SSH/new')} className="but25">添加SSH密钥</button>
</div>
{
list && list.length > 0 &&
@ -64,7 +64,7 @@ function SSH(props) {
{
list.map((i,k)=>{
return(
<List.Item>
<List.Item key={i.id}>
<img src={miyao} alt=""/>
<div>
<p className="color-grey-3"><a className="task-hide" style={{display:"block",fontWeight:"500"}} onClick={()=>{setContent(i);setVisibleDesc(true)}}>{i.name}</a></p>

View File

@ -0,0 +1,43 @@
export const noticeSourceType = {
// 易修
IssueAssigned:"icon-yixiuicon1", // 有新指派给我的易修
IssueAssignerExpire:"icon-yixiuicon1", // 我负责的易修截止日期到达最后一天
IssueAtme:"icon-yixiuicon1", // 在易修中@我
IssueChanged:"icon-yixiuicon1", // 我创建或负责的易修状态变更
IssueCreatorExpire:"icon-yixiuicon1", // 我创建的易修截止日期到达最后一天
IssueDelete:"icon-yixiuicon1", // 我创建或负责的易修删除
IssueDeleted:"icon-yixiuicon1", // 我创建或负责的易修删除
IssueJournal:"icon-yixiuicon1", // 我创建或负责的易修有新的评论
//平台通知
LoginIpTip:"icon-xitongtongzhiicon", //登录异常提示
//个人状态类通知
OrganizationJoined:"icon-xiaoxi2", // 账号被拉入组织
OrganizationLeft:"icon-xiaoxi2", // 账号被移出组织
OrganizationRole:"icon-xiaoxi2", // 账号组织权限变更
ProjectJoined:"icon-xiaoxi2", // 账号被拉入项目
ProjectLeft:"icon-xiaoxi2", // 账号被移出项目
ProjectRole:"icon-xiaoxi2", // 账号仓库权限变更
//其他仓库通知
ProjectDelete:"icon-daimakuicon1", // 我关注的仓库被删除
ProjectFollowed:"icon-daimakuicon1", // 我管理的仓库被关注
ProjectForked:"icon-daimakuicon1", // 我管理的仓库被复刻
ProjectIssue:"icon-daimakuicon1", // 我管理/关注的仓库有新的易修
ProjectSettingChanged:"icon-daimakuicon1", // 我管理的仓库项目设置被更改
ProjectTransfer:"icon-daimakuicon1", // 我关注的仓库被转移
ProjectVersion:"icon-daimakuicon1", // 我关注的仓库有新的发行版
ProjectMemberJoined:"icon-daimakuicon1", // 我管理的仓库有成员加入
ProjectMemberLeft:"icon-daimakuicon1", // 我管理的仓库有成员移出
ProjectPraised:"icon-daimakuicon1", // 我管理的仓库被点赞
//合并请求类通知
ProjectPullRequest:"icon-hebingqingqiuicon", // 我管理/关注的仓库有新的合并请求
PullRequestAssigned:"icon-hebingqingqiuicon", // 有新指派给我的合并请求
PullRequestAtme:"icon-hebingqingqiuicon", // 在合并请求中@我
PullRequestChanged:"icon-hebingqingqiuicon", // 我创建或负责的合并请求状态变更
PullRequestJournal:"icon-hebingqingqiuicon", // 我创建或负责的合并请求有新的评论
PullRequestClosed:"icon-hebingqingqiuicon", // 提交的合并请求被拒绝
PullRequestMerged:"icon-hebingqingqiuicon", // 提交的合并请求被合并
PullRequestClosed:"icon-hebingqingqiuicon", // 提交的合并请求被关闭
//里程碑
ProjectMilestone:"icon-lichengbeiicon", // 我管理的仓库有新的里程碑
};

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};
}

View File

@ -125,7 +125,7 @@ export default Form.create()(
</Form.Item>
<AlignCenter>
<span className="ant-form-item-label"></span>
<Button type={"primary"} onClick={submit}>提交</Button>
<Button className="but25" type={"primary"} onClick={submit}>提交</Button>
{/* <Button type={"default"} onClick={()=>props.history.push(`/${current_user && current_user.login}`)} className="ml20">取消</Button> */}
</AlignCenter>
</Form>

View File

@ -1,9 +1,10 @@
import React , { useEffect , useState } from 'react';
import './Index.scss';
import { Menu } from 'antd';
import { Link } from 'react-router-dom';
import Base from './Base';
import Password from './Password';
import './Index.scss';
import '../../SecuritySetting/notice/manager/Index.scss';
function Index(props){
// const { username } = props && props.match && props.match.params;
@ -25,11 +26,14 @@ function Index(props){
return(
<div>
<div className="notice01">
<div className="sshHead">
<Menu selectedKeys={[key]} mode={'horizontal'} className="infosRightMenu">
<Menu.Item key="0"><Link to={`/settings/profile`}>基本资料</Link></Menu.Item>
{/* <Menu.Item key="0"><Link to={`/settings/profile`}>基本资料</Link></Menu.Item> */}
{/* <Menu.Item key="1"><Link to={`/${username}/password`}>密码管理</Link></Menu.Item> */}
</Menu>
</div>
<div style={{padding:"20px"}}>
{
key === "0" ?

View File

@ -19,6 +19,12 @@
.ant-menu-item{
a{
font-size: 16px;
color: #333333;
position: relative;
bottom: -10px;
}
a:hover{
color: #333333;
}
}
}

View File

@ -89,7 +89,7 @@ export default Form.create()(
</Form.Item>
<AlignCenter style={{marginTop:"20px"}}>
<span className="ant-form-item-label"></span>
<Button type={"primary"} onClick={submit}>提交</Button>
<Button className="but25" type={"primary"} onClick={submit}>提交</Button>
<Button type={"default"} onClick={()=>props.history.push(`/${username}`)} className="ml20">取消</Button>
</AlignCenter>
</Form>