diff --git a/package-lock.json b/package-lock.json index 628e5a3b0..4771ae99a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index a78351cba..ff7a86851 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/css/iconfont.css b/public/css/iconfont.css index 4498c693a..7e0e2c7e6 100644 --- a/public/css/iconfont.css +++ b/public/css/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 2340181 */ - src: url('iconfont.woff2?t=1630632852475') format('woff2'), - url('iconfont.woff?t=1630632852475') format('woff'), - url('iconfont.ttf?t=1630632852475') format('truetype'); + src: url('iconfont.woff2?t=1631773579834') format('woff2'), + url('iconfont.woff?t=1631773579834') format('woff'), + url('iconfont.ttf?t=1631773579834') format('truetype'); } .iconfont { @@ -13,6 +13,58 @@ -moz-osx-font-smoothing: grayscale; } +.icon-xiangmubiaoqian:before { + content: "\e8da"; +} + +.icon-icon:before { + content: "\e8ce"; +} + +.icon-tar:before { + content: "\e8cf"; +} + +.icon-a-fuzhi2:before { + content: "\e8d0"; +} + +.icon-fujian1:before { + content: "\e8d1"; +} + +.icon-a-bianji1:before { + content: "\e8d2"; +} + +.icon-banbenicon:before { + content: "\e8d3"; +} + +.icon-shanchuicon2:before { + content: "\e8d4"; +} + +.icon-a-lajitong_icon3x:before { + content: "\e8d5"; +} + +.icon-xialaanniu2:before { + content: "\e8d6"; +} + +.icon-xiazai-icon:before { + content: "\e8d7"; +} + +.icon-master_icon1:before { + content: "\e8d8"; +} + +.icon-shangchuanicon:before { + content: "\e8d9"; +} + .icon-gerenziliao1:before { content: "\e8c7"; } diff --git a/public/css/iconfont.js b/public/css/iconfont.js index e50f44e1a..f38ef2a3a 100644 --- a/public/css/iconfont.js +++ b/public/css/iconfont.js @@ -1 +1 @@ -!function(c){var l,a,h,i,o,z='',t=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss"),p=function(c,l){l.parentNode.insertBefore(c,l)};if(t&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}function v(){o||(o=!0,h())}function m(){try{i.documentElement.doScroll("left")}catch(c){return void setTimeout(m,50)}v()}l=function(){var c,l;(l=document.createElement("div")).innerHTML=z,z=null,(c=l.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",l=c,(c=document.body).firstChild?p(l,c.firstChild):c.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(a=function(){document.removeEventListener("DOMContentLoaded",a,!1),l()},document.addEventListener("DOMContentLoaded",a,!1)):document.attachEvent&&(h=l,i=c.document,o=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,v())})}(window); +!function(c){var a,l,h,i,o,z='',t=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss"),p=function(c,a){a.parentNode.insertBefore(c,a)};if(t&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}function v(){o||(o=!0,h())}function m(){try{i.documentElement.doScroll("left")}catch(c){return void setTimeout(m,50)}v()}a=function(){var c,a;(a=document.createElement("div")).innerHTML=z,z=null,(c=a.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",a=c,(c=document.body).firstChild?p(a,c.firstChild):c.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(a,0):(l=function(){document.removeEventListener("DOMContentLoaded",l,!1),a()},document.addEventListener("DOMContentLoaded",l,!1)):document.attachEvent&&(h=a,i=c.document,o=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,v())})}(window); \ No newline at end of file diff --git a/public/css/iconfont.json b/public/css/iconfont.json index e78a264c5..42dec3471 100644 --- a/public/css/iconfont.json +++ b/public/css/iconfont.json @@ -5,6 +5,97 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "24378423", + "name": "项目标签", + "font_class": "xiangmubiaoqian", + "unicode": "e8da", + "unicode_decimal": 59610 + }, + { + "icon_id": "24368060", + "name": "icon", + "font_class": "icon", + "unicode": "e8ce", + "unicode_decimal": 59598 + }, + { + "icon_id": "24368061", + "name": "tar", + "font_class": "tar", + "unicode": "e8cf", + "unicode_decimal": 59599 + }, + { + "icon_id": "24289113", + "name": "复制 (2)", + "font_class": "a-fuzhi2", + "unicode": "e8d0", + "unicode_decimal": 59600 + }, + { + "icon_id": "24289114", + "name": "附件", + "font_class": "fujian1", + "unicode": "e8d1", + "unicode_decimal": 59601 + }, + { + "icon_id": "24289115", + "name": "编 辑", + "font_class": "a-bianji1", + "unicode": "e8d2", + "unicode_decimal": 59602 + }, + { + "icon_id": "24289116", + "name": "版本icon", + "font_class": "banbenicon", + "unicode": "e8d3", + "unicode_decimal": 59603 + }, + { + "icon_id": "24289117", + "name": "删除icon", + "font_class": "shanchuicon2", + "unicode": "e8d4", + "unicode_decimal": 59604 + }, + { + "icon_id": "24289118", + "name": "垃圾桶_icon@3x", + "font_class": "a-lajitong_icon3x", + "unicode": "e8d5", + "unicode_decimal": 59605 + }, + { + "icon_id": "24289119", + "name": "下拉按钮", + "font_class": "xialaanniu2", + "unicode": "e8d6", + "unicode_decimal": 59606 + }, + { + "icon_id": "24289120", + "name": "下载-icon", + "font_class": "xiazai-icon", + "unicode": "e8d7", + "unicode_decimal": 59607 + }, + { + "icon_id": "24289121", + "name": "master_icon", + "font_class": "master_icon1", + "unicode": "e8d8", + "unicode_decimal": 59608 + }, + { + "icon_id": "24289122", + "name": "上传icon", + "font_class": "shangchuanicon", + "unicode": "e8d9", + "unicode_decimal": 59609 + }, { "icon_id": "24059956", "name": "个人资料", diff --git a/public/css/iconfont.ttf b/public/css/iconfont.ttf index a0b8934f1..a559db11e 100644 Binary files a/public/css/iconfont.ttf and b/public/css/iconfont.ttf differ diff --git a/public/css/iconfont.woff b/public/css/iconfont.woff index 98e387c01..2076f553a 100644 Binary files a/public/css/iconfont.woff and b/public/css/iconfont.woff differ diff --git a/public/css/iconfont.woff2 b/public/css/iconfont.woff2 index eb219c96e..3031b14a1 100644 Binary files a/public/css/iconfont.woff2 and b/public/css/iconfont.woff2 differ diff --git a/public/js/editormd/editormd.min.js b/public/js/editormd/editormd.min.js index 0add3b370..860a4de0e 100755 --- a/public/js/editormd/editormd.min.js +++ b/public/js/editormd/editormd.min.js @@ -3319,9 +3319,9 @@ text = text.replace(emailReg, function ($1, $2, $3, $4) { return $1.replace(/@/g, "_#_@_#_"); }); - + // " + editormd.urls.atLinkBase + "" + $2 + " text = text.replace(atLinkReg, function ($1, $2) { - return "" + $1 + ""; + return " " + $1 + " "; }).replace(/_#_@_#_/g, "@"); } diff --git a/src/forge/Component/ModalFun/index.jsx b/src/forge/Component/ModalFun/index.jsx new file mode 100644 index 000000000..8fee2c73e --- /dev/null +++ b/src/forge/Component/ModalFun/index.jsx @@ -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 + + {props.contentTitle} + } + /> + } else if (type === 'confirm') { + return + } else { + return + } + } + + 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 ( + + {cancelText} + , + , + ]} + > +
+ {contentTitle &&

{contentTitle}

} +

{content}

+
+
+ ) +} diff --git a/src/forge/Component/ModalFun/index.scss b/src/forge/Component/ModalFun/index.scss new file mode 100644 index 000000000..24f7b0219 --- /dev/null +++ b/src/forge/Component/ModalFun/index.scss @@ -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; + } +} \ No newline at end of file diff --git a/src/forge/Component/Releases.jsx b/src/forge/Component/Releases.jsx index 6d4d0acf4..3983f600f 100644 --- a/src/forge/Component/Releases.jsx +++ b/src/forge/Component/Releases.jsx @@ -27,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 ( +
+
+ setNoticeType(e.key)}> + 系统通知 + {/* 私信 */} + @我 + +
+ + {/* 系统通知 */} + {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/CoderRootTag.js b/src/forge/Main/CoderRootTag.js index da1fa96d1..484836213 100644 --- a/src/forge/Main/CoderRootTag.js +++ b/src/forge/Main/CoderRootTag.js @@ -34,7 +34,7 @@ export default (( props, { projectDetail }) => {
  • - 标签名 + 标记名 提交信息 下载
  • diff --git a/src/forge/Main/Detail.js b/src/forge/Main/Detail.js index 560420a66..a1a741496 100644 --- a/src/forge/Main/Detail.js +++ b/src/forge/Main/Detail.js @@ -255,7 +255,11 @@ class Detail extends Component { open_devops: flag }) } - canvasChannel = () => { + /** + * + * @param {*} deleteFlag :同步镜像需要提示成功,且未成功的情况下不需要删除项目 + */ + canvasChannel = (deleteFlag) => { const name = window.location.hostname === "localhost" ? "testforgeplus.trustie.net" : window.location.hostname; const actioncable = require("actioncable"); var project = this.state.project; @@ -272,10 +276,15 @@ class Detail extends Component { console.log(`###### ---received data--- ######`); console.log(data); if (data) { - if ( data.project && data.project.mirror_status === 2) { - this.deleteProjectBack(); + if(deleteFlag){ + this.props.showNotification("镜像同步成功!"); + window.location.reload(); + }else{ + if (data.project && data.project.mirror_status === 2) { + this.deleteProjectBack(); + } + this.getDetail(); } - this.getDetail(); this.setState({ firstSync: false, secondSync: false @@ -428,8 +437,10 @@ class Detail extends Component { const url = `/${owner}/${projectsId}/sync_mirror.json`; axios.post(url).then(result => { if (result && result.data && result.data.status === 0) { - this.props.showNotification("镜像同步成功!"); - this.getProject(2); + this.setState({ + secondSync:true + }) + this.canvasChannel(true); } else { this.props.showNotification("镜像同步失败!"); } diff --git a/src/forge/Merge/MessageCount.js b/src/forge/Merge/MessageCount.js index d1b46b865..b064e3c2f 100644 --- a/src/forge/Merge/MessageCount.js +++ b/src/forge/Merge/MessageCount.js @@ -424,7 +424,7 @@ class MessageCount extends Component { - 标签: + 标记: {data.issue.issue_tags && data.issue.issue_tags.length > 0 diff --git a/src/forge/Merge/merge.js b/src/forge/Merge/merge.js index 1e1285e4e..399c9bd91 100644 --- a/src/forge/Merge/merge.js +++ b/src/forge/Merge/merge.js @@ -43,7 +43,7 @@ class merge extends Component { //设置选择高亮 openselect: 1, closeselect: undefined, - issue_tag_ids: "标签", + issue_tag_ids: "标记", fixed_version_ids: "里程碑", assigned_to_ids: "审查人员", paix: "排序", @@ -198,7 +198,7 @@ class merge extends Component { }); this.setState({ status_type: type, - issue_tag_ids: "标签", + issue_tag_ids: "标记", fixed_version_ids: "里程碑", assigned_to_ids: "审查人员", paix: "排序", @@ -321,7 +321,7 @@ class merge extends Component { className="topWrapperSelect" overlay={this.renderMenu( issue_chosen && issue_chosen.issue_tag, - "标签", + "标记", "issue_tag_id" )} trigger={["click"]} diff --git a/src/forge/Merge/merge_form.js b/src/forge/Merge/merge_form.js index 9332d9b85..5df9794bb 100644 --- a/src/forge/Merge/merge_form.js +++ b/src/forge/Merge/merge_form.js @@ -300,7 +300,8 @@ class MergeForm extends Component { {getFieldDecorator("assigned_to_id", { initialValue: assigned_to_id, })( - + {this.renderSelect(members)} )} @@ -311,12 +312,11 @@ class MergeForm extends Component { })( )} @@ -327,12 +327,11 @@ class MergeForm extends Component { })( )} diff --git a/src/forge/New/Index.js b/src/forge/New/Index.js index 832f0a1a4..c6eb2c3d5 100644 --- a/src/forge/New/Index.js +++ b/src/forge/New/Index.js @@ -219,7 +219,7 @@ class Index extends Component { user_id:owners_id }).then((result) => { if (result && result.data.id) { - projectsType && projectsType !== "mirror" && this.props.showNotification(`托管项目创建成功!`); + projectsType && projectsType !== "mirror" && this.props.showNotification(`项目创建成功!`); this.props.history.push(`/${result.data.login}/${result.data.identifier}`); } this.setState({ @@ -351,7 +351,7 @@ class Index extends Component { required: true, message: '请填写镜像版本库地址' }], })( - + )}

    示例:https://github.com/facebook/reack.git

    @@ -386,7 +386,7 @@ class Index extends Component { {getFieldDecorator('password', { rules: [], })( - + )}
diff --git a/src/forge/Nodata.js b/src/forge/Nodata.js index 3147e5601..ccc74ac54 100644 --- a/src/forge/Nodata.js +++ b/src/forge/Nodata.js @@ -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; diff --git a/src/forge/Notice/Index.jsx b/src/forge/Notice/Index.jsx index 3f61688fb..73a2561bd 100644 --- a/src/forge/Notice/Index.jsx +++ b/src/forge/Notice/Index.jsx @@ -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 (
    -
  • - - 通知 - {messagesCount ? {messagesCount}:""} - -
  • - + 接收仓库 {transferCount ? {transferCount}:""} @@ -109,16 +95,10 @@ function Index(props){ return ; }} > - { - return ; - }} - > { - return ; + return ; }} > diff --git a/src/forge/Order/Detail.js b/src/forge/Order/Detail.js index 8805004b9..b377b6a1d 100644 --- a/src/forge/Order/Detail.js +++ b/src/forge/Order/Detail.js @@ -315,7 +315,7 @@ class Detail extends Component {

    - 标签: + 标记: {data && data.issue_tags ? ( diff --git a/src/forge/Order/MilepostDetail.js b/src/forge/Order/MilepostDetail.js index c1c8bdd06..0f5d191e0 100644 --- a/src/forge/Order/MilepostDetail.js +++ b/src/forge/Order/MilepostDetail.js @@ -40,7 +40,7 @@ class MilepostDetail extends Component { issue_type: undefined, status_type: '1', //设置选择高亮 - issue_tag_ids: '标签', + issue_tag_ids: '标记', tracker_ids: '类型', author_ids: '发布人', assigned_to_ids: '负责人', @@ -194,7 +194,7 @@ class MilepostDetail extends Component { done_ratio : undefined, status_id: undefined, assigned_to_id: undefined, - issue_tag_ids: '标签', + issue_tag_ids: '标记', tracker_ids: '类型', author_ids: '发布人', assigned_to_ids: '负责人', @@ -254,7 +254,7 @@ class MilepostDetail extends Component {

  • - + {this.state.issue_tag_ids}
  • diff --git a/src/forge/Order/Nav.js b/src/forge/Order/Nav.js index b5d196d61..3f5a20c59 100644 --- a/src/forge/Order/Nav.js +++ b/src/forge/Order/Nav.js @@ -7,7 +7,7 @@ class Nav extends Component{ const { projectsId , owner } = this.props.match.params; return(

    - 标签 + 标记 里程碑

    ) diff --git a/src/forge/Order/Tags.js b/src/forge/Order/Tags.js index 174891aa6..eeff8db28 100644 --- a/src/forge/Order/Tags.js +++ b/src/forge/Order/Tags.js @@ -324,11 +324,11 @@ class Tags extends Component { data && data.issue_tags && data.issue_tags.length > 0 ?
    - 共{data && data.issue_tags_count}个标签 + 共{data && data.issue_tags_count}个标记
    • - 标签 + 标记
    diff --git a/src/forge/Order/order.js b/src/forge/Order/order.js index 290bb03e3..7275c759a 100644 --- a/src/forge/Order/order.js +++ b/src/forge/Order/order.js @@ -39,7 +39,7 @@ class order extends Component { search_count: undefined, issue_type: undefined, status_type: "1", // 默认显示开启中的 - issue_tag_ids: "标签", + issue_tag_ids: "标记", tracker_ids: "类型", author_ids: "发布人", assigned_to_ids: "负责人", @@ -58,7 +58,7 @@ class order extends Component { select_params: { assigned_to_id: undefined, // 负责人 author_id: undefined, // 发布人 - issue_tag_id: undefined, // 标签 + issue_tag_id: undefined, // 标记 tracker_id: undefined, //类型 done_ratio: undefined, // 完成度 status_id: undefined, // 优先级 @@ -257,7 +257,7 @@ class order extends Component { author_id: undefined, assigned_to_id: undefined, status_type: type, - issue_tag_ids: "标签", + issue_tag_ids: "标记", tracker_ids: "类型", author_ids: "发布人", assigned_to_ids: "负责人", @@ -677,7 +677,7 @@ class order extends Component { className="topWrapperSelect" overlay={this.renderMenu( issue_chosen && issue_chosen.issue_tag, - "标签", + "标记", "issue_tag_id" )} trigger={["click"]} diff --git a/src/forge/Order/order_form.js b/src/forge/Order/order_form.js index 6515dde86..936dc3d11 100644 --- a/src/forge/Order/order_form.js +++ b/src/forge/Order/order_form.js @@ -442,13 +442,13 @@ class order_form extends Component { )} - + {getFieldDecorator("issue_tag_ids", {rules: []})( diff --git a/src/forge/SecuritySetting/Index.jsx b/src/forge/SecuritySetting/Index.jsx index d9d4e49c2..8419fd31c 100644 --- a/src/forge/SecuritySetting/Index.jsx +++ b/src/forge/SecuritySetting/Index.jsx @@ -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){ {current_user && current_user.username}
    -
      +
      • 个人信息
      • -
      • -1 ?"active":""}>基本资料
      • +
      • -1 ?"active":""}>基本资料
      • +
      +
        +
      • 消息通知
      • +
      • -1 && pathname.indexOf("/settings/notice/config") == -1) || pathname.indexOf("/settings/notice/privateLetter")>-1 ?"active":""}>我的通知
      • + {/*
      • -1 ?"active":""}>通知管理
      • */}
      • 安全设置
      • -
      • -1 ?"active":""}>SSH密钥
      • +
      • -1 ?"active":""}>SSH密钥
    + ( + + )} + > + ( + + )} + > ( @@ -69,12 +102,17 @@ function Index(props){ )} > + ( + + )} + >
- ) } diff --git a/src/forge/SecuritySetting/Index.scss b/src/forge/SecuritySetting/Index.scss index f533f0530..ede91c86d 100644 --- a/src/forge/SecuritySetting/Index.scss +++ b/src/forge/SecuritySetting/Index.scss @@ -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; - } - &:first-child{ - font-size: 16px; + .text-shodow-bold{ + font-weight: 400; + text-shadow: 0.5px 0 #333; + } } &.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; } @@ -147,4 +174,23 @@ flex:1; } } +} +.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; } \ No newline at end of file diff --git a/src/forge/SecuritySetting/img/miyao_middle_icon.png b/src/forge/SecuritySetting/img/miyao_middle_icon.png index 03584cfe3..f69cba0e8 100644 Binary files a/src/forge/SecuritySetting/img/miyao_middle_icon.png and b/src/forge/SecuritySetting/img/miyao_middle_icon.png differ diff --git a/src/forge/SecuritySetting/notice/api.js b/src/forge/SecuritySetting/notice/api.js new file mode 100644 index 000000000..07fc1838a --- /dev/null +++ b/src/forge/SecuritySetting/notice/api.js @@ -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, + }); +} diff --git a/src/forge/SecuritySetting/notice/fetch.js b/src/forge/SecuritySetting/notice/fetch.js new file mode 100644 index 000000000..dba00b140 --- /dev/null +++ b/src/forge/SecuritySetting/notice/fetch.js @@ -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; \ No newline at end of file diff --git a/src/forge/SecuritySetting/notice/manager/Index.jsx b/src/forge/SecuritySetting/notice/manager/Index.jsx new file mode 100644 index 000000000..ebe84beac --- /dev/null +++ b/src/forge/SecuritySetting/notice/manager/Index.jsx @@ -0,0 +1,100 @@ +import { Button, Checkbox } from "antd"; +import React from "react"; + +import './Index.scss'; + +function NoticeManager(props){ + + return( +
+
+ 通知管理 +
+
+ 您可以通过通知管理来选择接受通知的方式 +
+ 我创建或负责的 +
+
+
易修状态变更
+ 站内信 + 邮件 +
+
+
易修截止日期到达最后一天
+ 站内信 + 邮件 +
+
+
合并请求状态变更
+ 站内信 + 邮件 +
+
+
易修有新的评论
+ 站内信 + 邮件 +
+
+
合并请求有新的评论
+ 站内信 + 邮件 +
+ +
+ 我管理的仓库 +
+
+
被关注
+ 站内信 + 邮件 +
+
+
被点赞
+ 站内信 + 邮件 +
+
+
被复刻
+ 站内信 + 邮件 +
+
+
有新的里程碑
+ 站内信 + 邮件 +
+ +
+ 我关注的仓库 +
+
+
被删除
+ 站内信 + 邮件 +
+
+
被转移
+ 站内信 + 邮件 +
+
+
有新的易修
+ 站内信 + 邮件 +
+
+
有新的合并请求
+ 站内信 + 邮件 +
+
+
有新的版本发布
+ 站内信 + 邮件 +
+
+
+ ) +} +export default NoticeManager; \ No newline at end of file diff --git a/src/forge/SecuritySetting/notice/manager/Index.scss b/src/forge/SecuritySetting/notice/manager/Index.scss new file mode 100644 index 000000000..87a77a758 --- /dev/null +++ b/src/forge/SecuritySetting/notice/manager/Index.scss @@ -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; + } +} \ No newline at end of file diff --git a/src/forge/SecuritySetting/notice/myNotice/Index.jsx b/src/forge/SecuritySetting/notice/myNotice/Index.jsx new file mode 100644 index 000000000..2de2b8c3f --- /dev/null +++ b/src/forge/SecuritySetting/notice/myNotice/Index.jsx @@ -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 ( +
+
+ + 系统通知 + {/* 私信 */} + @我 + + {(noticeType==="0" && noticeUnreadCount>0) || (noticeType==="2"&& atUnreadCount>0) ? :""} +
+ +
+
+ {onlyUnread===1 || messageList && messageList.length>0 ? e.target.checked ? setOnlyUnread(1) : setOnlyUnread()}>仅看未读{noticeType === "1" ? `私信(12)` : noticeType === "0" ? `消息(${noticeUnreadCount})` : `消息(${atUnreadCount})`}:""} +
+ {noticeType === "2" && messageList && messageList.length > 0 ? : ""} +
+ + {messageList && messageList.length===0 ? :""} + + {messageList && messageList.length>0 &&
+
+ 全选 +    已选择 {selectedNum} 项 +
+
+      + +
+
} + + {messageList && messageList.map(item => { + // 系统消息 + if (noticeType === "0") { + // 消息类别 + return ( +
+
+ {item.status === 1 ? : } + + {turnToMess(item)}} dangerouslySetInnerHTML={{__html: item.content}}> +
+
+ {item.time_ago} + {item.status === 1 && readNotice([item.id])}>标记为已读} +
+
+ ) + } else if (noticeType === "2") { + //@我 + return ( +
+
+ + {item.sender && {window.open(`/${item.sender && item.sender.login}`);}}/>} +
{turnToMess(item)}}> + {item.status === 1 ? : } + {item.sender && " + item.sender.name+ " "+ item.content +" 中@我"}}>} +
+
+
+ {item.time_ago} + {!isBatchDelete && item.status === 1 && readNotice([item.id])}>标记为已读}    + {!isBatchDelete && deleteNotice([item.id])}>删除} +
+
+ ) + } else{ + //私信 + {/*
+ +
+
+ 蒋宇航 + 4分钟前 + 删除 +
+
props.history.push('/settings/notice/privateLetter')}> + 最好的OpenStack控制台,对标OpenStack社区Horizon项目,最好的OpenStack控制台,对标OpenStack社区Horizon项目,在易用性、页面性能等方面进行深度优化,提供简单控制台。 +
+
+
*/} + } + })} + + {/* 分页 */} + {!isBatchDelete &&
+ {setCurrentPage(page)}} + total = {messTotalCount} + hideOnSinglePage + > +
} +
+ ) +} +export default MyNotice; \ No newline at end of file diff --git a/src/forge/SecuritySetting/notice/myNotice/Index.scss b/src/forge/SecuritySetting/notice/myNotice/Index.scss new file mode 100644 index 000000000..8eab84c0c --- /dev/null +++ b/src/forge/SecuritySetting/notice/myNotice/Index.scss @@ -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; +} \ No newline at end of file diff --git a/src/forge/SecuritySetting/notice/privateLetter/Index.jsx b/src/forge/SecuritySetting/notice/privateLetter/Index.jsx new file mode 100644 index 000000000..517583770 --- /dev/null +++ b/src/forge/SecuritySetting/notice/privateLetter/Index.jsx @@ -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( +
+
+
+ + 蒋宇航 +
+
+
+ +
+
+ 嗨在吗?哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 + 删除 +
+ 2021-08-29 11:59 +
+ +
+ +
+
+ 哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 + 删除 +
+ 2021-08-29 11:59 +
+ +
+
+
+ +