@@ -28,7 +27,7 @@ function Releases({owner,projectsId,releaseVersions , baseOperate , projectType}
})
:
- 您暂未发布任何版本{baseOperate && projectType !==2 && 创建新版本}
+ 您暂未发布任何版本{baseOperate && projectType !==2 && 创建新版本}
}
diff --git a/src/forge/Head/AppPullRefresh.jsx b/src/forge/Head/AppPullRefresh.jsx
new file mode 100644
index 000000000..2b9c76606
--- /dev/null
+++ b/src/forge/Head/AppPullRefresh.jsx
@@ -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
显示更多
+ case 1: // 加载中
+ return (
+
+
+ 加载中...
+
+ )
+ case 2: // 无样式
+ return
没有更多了
+ default:
+ return
没有更多了
+ }
+ }
+
+
+ render() {
+ const { className, count, children } = this.props;
+ return (
+
{ this.pullRef = dom }}
+ >
+
+ {children}
+
+ {
+ count < 1 &&
+ }
+
+ {/* 大于分页数据才显示loading */}
+ {/* {this.props.count >= this.props.pageSize ? this.renderLoading() : null} */}
+
+
+
+ )
+ }
+
+}
+
+
+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
diff --git a/src/forge/Head/Header.js b/src/forge/Head/Header.js
index be36d6dbd..41d7fcfd3 100644
--- a/src/forge/Head/Header.js
+++ b/src/forge/Head/Header.js
@@ -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 {
)
}
+
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 {
}
- {/* {search_url ? this.SearchInput(openSearch,search_url):""} */}
{ search_url &&
}
{
current_user && (current_user.main_site || current_user.login) && (settings && settings.add && settings.add.length>0)?
-
+
:""
}
- {this.props.user && this.props.user.login && notice_url ?
-
- {user && user.login &&
-
-
-
-
-
- }
-
:""
+ {current_user && current_user.login ?
+
}
+ visible={visible}
+ onVisibleChange={this.handleVisibleChange}
+ destroyTooltipOnHide
+ >
+
+
+
+
+
+
+ : ""
}
{!user || (user && !user.login) ?
diff --git a/src/forge/Head/NoticeContent.jsx b/src/forge/Head/NoticeContent.jsx
new file mode 100644
index 000000000..634a7adc8
--- /dev/null
+++ b/src/forge/Head/NoticeContent.jsx
@@ -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, //是否已读,0未读,1已读
+ // 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 (
+
+
+
+
+
+ {/* 系统通知 */}
+ {noticeType === "notification" &&
{ setNoticePage(noticePage + 1); }} //触发加载ajax的function
+ // type={2} // 传送加载组件的状态
+ count={noticeUnreadList.length} // 数据当前的总数量
+ pageSize={10} //
+ >
+ {
+ noticeUnreadList.map(item => {
+ return (
+ { readItem(item) }}>
+
+
+
+
+
+
+
+ {item.time_ago}
+
+
+
+ )
+ })
+ }
+
+ }
+
+ {/* @我 */}
+ {noticeType === "atme" &&
{ setAtPage(atPage + 1); }} //触发加载ajax的function
+ // type={1} // 传送加载组件的状态
+ count={atUnreadList.length} // 数据当前的总数量
+ pageSize={10} //
+ >
+ {atUnreadList.map(item => {
+ return (
+ { readItem(item) }}>
+
+
+
+
+
+ " + (item.sender ? item.sender.name : '') + " " + item.content + " 中@我" }}>
+ {item.time_ago}
+
+
+
+ )
+ })
+ }
+
+ }
+
+ {/* 私信 */}
+ {/* {noticeType === "1" ? letter_unread_list.length > 0 ? letter_unread_list.map(item => {
+ return (
+
+
= 30 && item.content.length <= 34 ? '65px' : "" }}>
+
+
+ {item.send_name}:
+ = 50 ? item.content.substr(0, 50) + "..." : item.content }}>
+ {item.create_time ? timeAgo(item.create_time) : "刚刚"}
+
+
+
+ )
+ }) : "暂无数据" : ""} */}
+
+
全部消息
+ {noticeUnreadCount > 0 && noticeType === "notification" &&
所有系统消息一键已读}
+ {atUnreadCount > 0 && noticeType === "atme" &&
所有@我一键已读}
+
+
+
+ )
+}
+export default NoticeContent;
diff --git a/src/forge/Head/header.scss b/src/forge/Head/header.scss
index fc8ca9d62..263cd1ee5 100644
--- a/src/forge/Head/header.scss
+++ b/src/forge/Head/header.scss
@@ -24,8 +24,9 @@
width: 34px;
height: 34px;
border-radius: 50%;
- margin-left: 30px;
+ margin-left: 15px;
}
+
.currentMenu{
width: 120px;
text-align: center;
@@ -126,4 +127,139 @@
width: 110px;
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;
}
\ No newline at end of file
diff --git a/src/forge/Main/CoderDepot.jsx b/src/forge/Main/CoderDepot.jsx
index 02a829a7b..545b92fe1 100644
--- a/src/forge/Main/CoderDepot.jsx
+++ b/src/forge/Main/CoderDepot.jsx
@@ -67,14 +67,14 @@ function CoderDepot(props){
const [ editReadme , setEditReadme ] = useState(false);
const [ pullsFlag , setPullsFlag ] = useState(true);
const [ issuesFlag , setIssuesFlag ] = useState(true);
+ const [ releaseVersions , setReleaseVersions] = useState(undefined);
+ const details = props.projectDetail;
const owner = props.match.params.owner;
const projectsId = props.match.params.projectsId;
let branchName = props.match.params.branchName;
branchName = returnbar(branchName);
- const details = props.projectDetail;
let pathname = props.history.location.pathname;
-
const { bannerList } = props;
useEffect(()=>{
@@ -90,7 +90,6 @@ function CoderDepot(props){
}
},[bannerList])
-
useEffect(()=>{
if(details){
setProjectDetail(details);
@@ -100,7 +99,7 @@ function CoderDepot(props){
setDefaultBranch(details.default_branch);
setInviteCode(details.invite_code);
}
- },[details])
+ })
useEffect(()=>{
if(treeValue){
@@ -127,11 +126,22 @@ function CoderDepot(props){
}
},[projectsId,owner,pathname,defaultBranch])
+ useEffect(()=>{
+ axios.get(`/${owner}/${projectsId}/releases.json`).then((result)=>{
+ if(result && result.data && result.data.releases){
+ const release = {
+ "list":result.data.releases,
+ "total_count":result.data.releases.length
+ }
+ setReleaseVersions(release);
+ }
+ })
+ },[releaseVersions])
+
// 获取主目录列表
function getDirInfo(branch){
setIsSpin(true);
const url = `/${owner}/${projectsId}/entries.json`;
-
axios.get(url, {
params: { ref: branch }
}).then((result) => {
@@ -521,13 +531,13 @@ function CoderDepot(props){
}
{/* 发布 */}
{
- projectDetail && projectDetail.release_versions &&
+ releaseVersions &&