Compare commits

...

37 Commits

Author SHA1 Message Date
caishi cf1905bc4b personal link 2021-08-09 10:58:27 +08:00
caishi fc5c957bf1 头部头像跳转到个人中心链接错误 2021-08-05 17:55:27 +08:00
caishi 8401a55e49 .json 2021-05-12 09:32:33 +08:00
caishi ee6961d02e 接口加上后缀.json 2021-05-11 18:12:12 +08:00
caishi 57ab5d1199 取消置顶接口名错误 2021-05-11 17:48:58 +08:00
caishi 6d9af5c906 未登录点击弹出登录框 2021-05-11 15:26:27 +08:00
caishi e38508ea59 未登录点击写点什么没有反应 2021-05-11 14:27:14 +08:00
caishi 68d1b69a09 a->link 2021-05-11 10:44:00 +08:00
caishi aef1451455 页面跳转时未滚动到顶部 2021-05-10 18:03:23 +08:00
caishi 191e79e05a d 2021-05-10 17:52:27 +08:00
caishi ea8b5ece1a 板块管理-背景色 2021-05-10 16:31:10 +08:00
caishi 41e9326174 router 2021-05-10 09:41:39 +08:00
caishi 8dad3b231c 底部信息 2021-05-08 17:20:52 +08:00
caishi 0703fb1bb6 antd-img-crop 2021-05-08 16:58:14 +08:00
caishi 7a96f7021a build 2020-10-28 15:37:00 +08:00
caishi 54fc16ddad Merge branch 'dev_wenba' of https://git.trustie.net/jasder/forgeplus-react into dev_wenba 2020-10-19 11:46:34 +08:00
caishi c04b2ed5f6 ismanager 2020-10-19 11:46:30 +08:00
sylor_huang@126.com 107b6ed95e Change NewHeader active 2020-10-16 17:12:28 +08:00
sylor_huang@126.com 67ccc2ebe5 Change List Count 2020-10-16 14:46:31 +08:00
sylor_huang@126.com c00933b0ca change2 2020-10-16 11:36:01 +08:00
sylor_huang@126.com 4f67719dac change children_forum_id 2020-10-15 15:48:01 +08:00
sylor_huang@126.com 5be45528cf Change page bugs 2020-10-15 14:38:36 +08:00
sylor_huang@126.com c3b2f8f6ad Change forums url /project to 404 2020-10-15 14:18:31 +08:00
sylor_huang@126.com 6950c1a820 Change Forum Url 2020-10-15 11:27:25 +08:00
sylor_huang@126.com b5881d5162 Change ForumUser Page1 2020-10-15 10:11:51 +08:00
sylor_huang@126.com 4de25d36a8 Merge branch 'dev_wenba' of http://git.trustie.net/jasder/forgeplus-react into dev_wenba 2020-10-15 10:01:32 +08:00
sylor_huang@126.com 5992e83c60 Change ForumUser Page 2020-10-15 10:01:21 +08:00
caishi c740dcc6f8 Merge branch 'dev_wenba' of https://git.trustie.net/jasder/forgeplus-react into dev_wenba 2020-10-15 09:50:05 +08:00
caishi 9922b109f8 spining 2020-10-15 09:50:02 +08:00
sylor_huang@126.com 7b097149eb Change ForumUser 2020-10-14 19:19:16 +08:00
caishi f7fa5291c6 exchange-板块管理 2020-10-14 18:15:36 +08:00
caishi 8ebac42d42 Merge branch 'dev_wenba' of https://git.trustie.net/jasder/forgeplus-react into dev_wenba 2020-10-14 17:07:52 +08:00
caishi 10db5271d7 编辑 2020-10-14 17:07:49 +08:00
sylor_huang@126.com 4596eac895 Change ForumSection Watch APi 2020-10-14 16:43:55 +08:00
caishi d1232be557 upload 2020-10-14 15:55:20 +08:00
sylor_huang@126.com 79c943d31b Change Memo Url 2020-10-13 14:39:22 +08:00
caishi 6d8834403a 问吧 2020-10-12 17:48:42 +08:00
152 changed files with 11948 additions and 245 deletions

65
package-lock.json generated
View File

@ -988,6 +988,30 @@
}
}
},
"antd-img-crop": {
"version": "3.14.1",
"resolved": "https://registry.npm.taobao.org/antd-img-crop/download/antd-img-crop-3.14.1.tgz",
"integrity": "sha1-ToJEJd7+op+xkTskYOXCOCokfGg=",
"requires": {
"@babel/runtime": "^7.13.4",
"react-easy-crop": "^3.3.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.14.0",
"resolved": "https://registry.nlark.com/@babel/runtime/download/@babel/runtime-7.14.0.tgz?cache=0&sync_timestamp=1619727414495&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.14.0.tgz",
"integrity": "sha1-RnlLwgthLF915i3QceJN/ZXxy+Y=",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.nlark.com/regenerator-runtime/download/regenerator-runtime-0.13.7.tgz",
"integrity": "sha1-ysLazIoepnX+qrrriugziYrkb1U="
}
}
},
"anymatch": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
@ -1273,7 +1297,7 @@
},
"babel-core": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz",
"resolved": "https://registry.npm.taobao.org/babel-core/download/babel-core-6.26.0.tgz",
"integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=",
"requires": {
"babel-code-frame": "^6.26.0",
@ -1935,7 +1959,7 @@
},
"babel-plugin-transform-runtime": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz",
"resolved": "https://registry.npm.taobao.org/babel-plugin-transform-runtime/download/babel-plugin-transform-runtime-6.23.0.tgz",
"integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
"requires": {
"babel-runtime": "^6.22.0"
@ -2005,7 +2029,7 @@
},
"babel-preset-react": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz",
"resolved": "https://registry.npm.taobao.org/babel-preset-react/download/babel-preset-react-6.24.1.tgz",
"integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=",
"requires": {
"babel-plugin-syntax-jsx": "^6.3.13",
@ -4663,7 +4687,7 @@
},
"dom-closest": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-closest/-/dom-closest-0.2.0.tgz",
"resolved": "https://registry.npm.taobao.org/dom-closest/download/dom-closest-0.2.0.tgz",
"integrity": "sha1-69n5HRvyLo1vR3h2u80+yQIWwM8=",
"requires": {
"dom-matches": ">=1.0.1"
@ -4707,7 +4731,7 @@
},
"dom-matches": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-matches/-/dom-matches-2.0.0.tgz",
"resolved": "https://registry.npm.taobao.org/dom-matches/download/dom-matches-2.0.0.tgz",
"integrity": "sha1-0nKLQWqHUzmA6wibhI0lPPI6dYw="
},
"dom-scroll-into-view": {
@ -4955,7 +4979,7 @@
},
"enquire.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz",
"resolved": "https://registry.npm.taobao.org/enquire.js/download/enquire.js-2.1.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fenquire.js%2Fdownload%2Fenquire.js-2.1.6.tgz",
"integrity": "sha1-PoeAybi4NQhMP2DhZtvDwqPImBQ="
},
"entities": {
@ -5474,7 +5498,7 @@
},
"eventlistener": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/eventlistener/-/eventlistener-0.0.1.tgz",
"resolved": "https://registry.npm.taobao.org/eventlistener/download/eventlistener-0.0.1.tgz",
"integrity": "sha1-7Suqu4UiJ68rz4iRUscsY8pTLrg="
},
"events": {
@ -7725,7 +7749,7 @@
},
"hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
"resolved": "https://registry.npm.taobao.org/hammerjs/download/hammerjs-2.0.8.tgz",
"integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE="
},
"handle-thing": {
@ -8566,7 +8590,7 @@
},
"immutable": {
"version": "3.7.6",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
"resolved": "https://registry.npm.taobao.org/immutable/download/immutable-3.7.6.tgz",
"integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks="
},
"import-fresh": {
@ -10154,7 +10178,7 @@
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"resolved": "https://registry.npm.taobao.org/lodash.throttle/download/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.uniq": {
@ -11013,6 +11037,11 @@
"sort-keys": "^1.0.0"
}
},
"normalize-wheel": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/normalize-wheel/download/normalize-wheel-1.0.1.tgz",
"integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU="
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@ -14809,6 +14838,22 @@
"prop-types": "^15.6.0"
}
},
"react-easy-crop": {
"version": "3.3.3",
"resolved": "https://registry.nlark.com/react-easy-crop/download/react-easy-crop-3.3.3.tgz?cache=0&sync_timestamp=1619179920392&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact-easy-crop%2Fdownload%2Freact-easy-crop-3.3.3.tgz",
"integrity": "sha1-ZllA2VwxKLD2tSxc2KXRpc26U4I=",
"requires": {
"normalize-wheel": "^1.0.1",
"tslib": "2.0.1"
},
"dependencies": {
"tslib": {
"version": "2.0.1",
"resolved": "https://registry.nlark.com/tslib/download/tslib-2.0.1.tgz?cache=0&sync_timestamp=1618846758811&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftslib%2Fdownload%2Ftslib-2.0.1.tgz",
"integrity": "sha1-QQ6w0RPltjVkkO7HSWA3JbAhtD4="
}
}
},
"react-error-overlay": {
"version": "6.1.0-next.80",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0-next.80.tgz",

View File

@ -7,6 +7,7 @@
"@novnc/novnc": "^1.1.0",
"actioncable": "^5.2.4-3",
"antd": "^3.26.15",
"antd-img-crop": "^3.14.1",
"array-flatten": "^2.1.2",
"autoprefixer": "7.1.6",
"axios": "^0.18.1",

View File

@ -2343,8 +2343,11 @@ input::-ms-clear {
/*中间部分宽度固定为1200*/
.newMain {
margin: 0 auto;
padding-bottom: 110px;
min-width: 1200px;
height: 100%;
min-height: 100%;
padding-top: 70px;
background-color: #fafafa;
}
@ -4106,20 +4109,7 @@ em.vertical-line {
/* 右侧内容宽度变化的话需要调整posi-search right的值*/
/*底部*/
.newFooter {
max-height: 110px;
}
.newFooter {
position: absolute;
bottom: 0;
width: 100%;
background: #323232;
clear: both;
min-width: 1200px;
z-index: 8;
left: 0px;
}
.footercon {
border-bottom: 1px solid #47494d;

View File

@ -2,22 +2,12 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name=”Keywords” Content=”trustie,trustieforge,forge,确实让创建更美好,协同开发平台″>
<meta name=”Keywords” Content=”TrustieOpenSourceProject″>
<meta name=”Keywords” Content=”issue,bug,tracker,软件工程,课程实践″>
<meta name=”Description” Content=持续构建协同、共享、可信的软件创建生态开源创作与软件生产相结合,支持大规模群体开展软件协同创新活动>
<meta name=”Keywords” Content="trustie,trustieforge,forge,确实让创建更美好,协同开发平台">
<meta name=”Keywords” Content="TrustieOpenSourceProject">
<meta name=”Keywords” Content="issue,bug,tracker,软件工程,课程实践">
<meta name=”Description” Content="持续构建协同、共享、可信的软件创建生态开源创作与软件生产相结合,支持大规模群体开展软件协同创新活动">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<!-- <script type="text/javascript">
window.__isR = true;
if (
(navigator.userAgent.indexOf('MSIE 9') != -1
|| navigator.userAgent.indexOf('MSIE 10') != -1)
&&
location.pathname.indexOf("/compatibility") == -1) {
location.href = '/compatibility.html'
}
</script> -->
<link rel=" stylesheet" type="text/css" href="%PUBLIC_URL%css/iconfont.css">
<link rel=" stylesheet" type="text/css" href="%PUBLIC_URL%css/edu-purge.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/editormd.min.css">

View File

@ -5,7 +5,8 @@ import zhCN from 'antd/lib/locale-provider/zh_CN';
import {
BrowserRouter as Router,
Route,
Switch
Switch,
Redirect
} from 'react-router-dom';
import axios from 'axios';
import LoginDialog from './modules/login/LoginDialog';
@ -42,10 +43,10 @@ const theme = createMuiTheme({
},
});
//forge项目
const Projects = Loadable({
loader: () => import('./forge/Index'),
loading: Loading,
})
// const Projects = Loadable({
// loader: () => import('./forge/Index'),
// loading: Loading,
// })
//forge项目-devOps详情
const OpsDetail = Loadable({
loader: () => import('./forge/DevOps/opsDetail'),
@ -61,22 +62,24 @@ const Shixunnopage = Loadable({
loader: () => import('./modules/404/Shixunnopage'),
loading: Loading,
})
const ExchangeForums = Loadable({
loader: () => import("./exchange/index"),
loading: Loading,
});
// 新版论坛交流
const Forums = Loadable({
loader: () => import("./forums/Index"),
loading: Loading,
});
const Account = Loadable({
loader: () => import("./user_info/Index"),
loading: Loading,
});
//500页面
const http500 = Loadable({
loader: () => import('./modules/500/http500'),
loading: Loading,
})
const InfosIndex = Loadable({
loader: () => import('./forge/users/Index'),
loading: Loading,
})
// 组织
const OrganizeIndex = Loadable({
loader: () => import('./forge/Team/Index'),
loading: Loading,
})
class App extends Component {
constructor(props) {
super(props);
@ -127,7 +130,7 @@ class App extends Component {
document.title = "loading...";
this.disableVideoContextMenu();
history.listen(() => {
this.forceUpdate()
this.forceUpdate();
const $ = window.$
$("html").animate({ scrollTop: $('html').scrollTop() - 0 })
});
@ -213,62 +216,43 @@ class App extends Component {
<Provider store={store}>
<ConfigProvider locale={zhCN}>
<MuiThemeProvider theme={theme}>
{/* <Accountnewprofile {...this.props}{...this.state} /> */}
<LoginDialog {...this.props} {...this.state} Modifyloginvalue={() => this.Modifyloginvalue()}></LoginDialog>
{/* <Notcompletedysl {...this.props} {...this.state}></Notcompletedysl> */}
{/* <Trialapplicationysl {...this.props} {...this.state}></Trialapplicationysl> */}
{/* <Trialapplicationreview {...this.props} {...this.state}></Trialapplicationreview> */}
{/* <AccountProfile {...this.props} {...this.state} /> */}
{/* <Certifiedprofessional {...this.props} {...this.state} ModalCancelsy={this.ModalCancelsy} ModalshowCancelsy={this.ModalshowCancelsy} /> */}
<Router>
<Router>
<Switch>
{/*项目*/}
<Route
path={"/projects/:projectId/ops/:opsId/detail"}
render={
(props) => {
return (<OpsDetail {...this.props} {...props} {...this.state} />)
}
}>
</Route>
{/*项目*/}
path="/accounts/:login"
render={(props) => (
<Account {...this.props} {...this.state} {...props} />
)}
></Route>
{/* 论坛交流板块管理 */}
<Route
path={"/projects"}
render={
(props) => {
return (<Projects {...this.props} {...props} {...this.state} />)
}
}>
</Route>
path="/forums/manage"
render={(props) => (
<ExchangeForums {...this.props} {...this.state} {...props} />
)}
></Route>
{/* 问吧、论坛交流 */}
<Route
path="/forums"
render={(props) => (
<Forums {...this.props} {...this.state} {...props} />
)}
></Route>
{/*403*/}
<Route path="/403" component={Shixunauthority} />
<Route path="/500" component={http500} />
<Route path={"/organize"}
render={
(props) => {
return (<OrganizeIndex {...this.props} {...props} {...this.state} />)
}
}>
</Route>
{/*404*/}
<Route path="/nopage" component={Shixunnopage} />
<Redirect from="/projects" to="/nopage"/>
{/* 个人主页 */}
<Route path="/users/:username"
render={
(props) => {
return (<InfosIndex {...this.props} {...this.state} />)
}
}></Route>
<Route exact path="/"
render={
(props) => (
<Projects {...this.props} {...props} {...this.state}></Projects>
<Forums {...this.props} {...this.state} {...props} />
)
}
/>
<Route component={Shixunnopage} />
</Switch>
</Router>
</MuiThemeProvider>

View File

@ -59,7 +59,7 @@ export function initAxiosInterceptors(props) {
// TODO 避免重复的请求 https://github.com/axios/axios#cancellation
var
proxy = "http://localhost:3000"
proxy = "https://testforgeplus.trustie.net"
proxy = "https://forum.trustie.net"
const requestMap = {};
window.setfalseInRequestMap = function (keyName) {

66
src/Nav/Footer.jsx Normal file
View File

@ -0,0 +1,66 @@
import React, { useEffect , useState } from 'react';
import './Index.scss';
function Footer(){
const [ value , setValue ] = useState(undefined);
useEffect(()=>{
try {
var chromesettingArray = JSON.parse(localStorage.getItem('chromesetting'));
setValue(chromesettingArray.footer);
} catch (e) {
}
},[])
function showhtml(htmlString){
var html = {__html:htmlString};
return <div dangerouslySetInnerHTML={html}></div> ;
}
return(
<div>
<div style={{height:"483px"}}></div>
<div className="newFooter edu-txt-center">
{value && showhtml(value)}
{/* <div className="footerInfos">
<ul>
<li>社区</li>
<li><a href={`/`} target="_blank">网站首页</a></li>
<li><a href={`https://www.trustie.net/agreement`} target="_blank">服务协议</a></li>
<li><a href={`https://forum.trustie.net/forums/1168/detail`} target="_blank">帮助中心</a></li>
<li><a href={`https://forum.trustie.net/`} target="_blank">问吧交流</a></li>
<li><a href={`https://www.trustie.net/cooperation`} target="_blank">合作伙伴</a></li>
</ul>
<ul>
<li>支持与服务</li>
<li><a href={`https://forgeplus.trustie.net/docs/api`} target="_blank">API文档</a></li>
<li><a href={`https://forum.trustie.net/forums/1168/detail`} target="_blank">帮助中心</a></li>
<li><a href={`https://git-scm.com`} target="_blank">Git常用命令</a></li>
<li><a href={`https://forum.trustie.net/forums/3080/detail`} target="_blank">DevOps使用文档</a></li>
<li><a href={`https://forgeplus.trustie.net/projects/jasder/forgeplus/tree/master/CHANGELOG.md`} target="_blank">日志更新</a></li>
</ul>
<ul>
<li>合作伙伴</li>
<li><a href={`http://www.sei.pku.edu.cn`} target="_blank">北京大学</a></li>
<li><a href={`http://scse.buaa.edu.cn`} target="_blank">北京航空航天大学</a></li>
<li><a href={`https://www.nju.edu.cn`} target="_blank">南京大学</a></li>
<li><a href={`https://www.xtu.edu.cn`} target="_blank">湘潭大学</a></li>
<li><a href={`http://www.iscas.ac.cn`} target="_blank">ISCAS</a></li>
<li><a href={`https://www.ucloud.cn`} target="_blank">UCloud优刻得</a></li>
<li><a href={`http://www.inforbus.com`} target="_blank">中创软件</a></li>
<li><a href={`https://www.inspur.com`} target="_blank">浪潮集团</a></li>
<li><a href={`http://www.copu.org.cn`} target="_blank">中国开源软件推进联盟</a></li>
<li><a href={`https://www.sjtu.edu.cn`} target="_blank">上海交通大学</a></li>
</ul>
<ul>
<li>合作伙伴</li>
<li><span>热线</span></li>
<li><span>QQ群1071514693</span></li>
</ul>
</div>
<p className="footerCopy">© Copyright 2007~2021 国防科技大学Trustie团队 & IntelliDE <a href="https://beian.miit.gov.cn">湘ICP备 17009477</a></p> */}
</div>
</div>
)
}
export default Footer;

68
src/Nav/Index.scss Normal file
View File

@ -0,0 +1,68 @@
.dropdownFlex{
display:flex;
padding:5px;
background:#fff;
border-radius: 3px;
.ant-menu-vertical > .ant-menu-item{
border:none;
height: 35px;
line-height: 35px;
margin:0px;
}
.ant-menu-vertical{
border:none;
}
}
.newFooter {
position: absolute;
bottom: 0;
width: 100%;
background: #323232;
clear: both;
min-width: 1200px;
z-index: 8;
left: 0px;
p {
margin-top: 0;
margin-bottom:0px !important;
}
.footerInfos{
display: flex;
max-width: 1200px;
margin:0px auto;
justify-content: space-around;
padding:60px 0px;
& >ul{
padding:0px 40px;
box-sizing: border-box;
max-width: 25%;
text-align: left;
li{
color: #fff;
font-weight: 300;
&:first-child{
font-size: 17px;
}
&>a,&>span{
color: #bbb;
}
&>a:hover{
color: #4cacff;
}
}
}
}
.footerCopy{
color: #bbb;
border-top: 1px solid #4e4e4e;
padding:10px 0px;
a{
color: #bbb;
&:hover{
color: #4cacff;
}
}
}
}

32
src/common/FileList.js Normal file
View File

@ -0,0 +1,32 @@
import React, { PureComponent } from 'react';
function bytesToSize(bytes) {
if (bytes === 0) return '0 B';
let k = 1024,
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)). toFixed(2) + ' ' + sizes[i];
}
class FileList extends PureComponent{
render(){
let { list , className } = this.props;
const listMap = list && list.map((item)=>{
return(
<li>
<i className="iconfont icon-fujian color-blue font-16 mr8"></i>
<a href={item.url} className="color-blue" target="_self">{item.filename}</a>
<span className="color-grey-9 ml10">{bytesToSize(`${item.filesize}`)}</span>
</li>
)
})
return(
<ul className={className}>
{ listMap }
</ul>
)
}
}
export default FileList;

343
src/common/MDEditor.js Normal file
View File

@ -0,0 +1,343 @@
import React, {Component} from 'react';
// import "antd/dist/antd.css";
import { getUrl } from 'educoder';
import './mdEditor.css';
require('codemirror/lib/codemirror.css');
let path = '/editormd/lib/'
path = getUrl("/editormd/lib/")
const $ = window.$;
// 保存数据
function md_add_data(k,mdu,d){
window.sessionStorage.setItem(k+mdu,d);
}
// 清空保存的数据
function md_clear_data(k,mdu,id){
window.sessionStorage.removeItem(k+mdu);
var id1 = "#e_tip_"+id;
var id2 = "#e_tips_"+id;
if(k == 'content'){
$(id2).html(" ");
}else{
$(id1).html(" ");
}
}
window.md_clear_data = md_clear_data
// editor 存在了jquery对象上应用不需要自己写md_rec_data方法了
function md_rec_data(k, mdu, id) {
if (window.sessionStorage.getItem(k + mdu) !== null) {
var editor = $("#e_tips_" + id).data('editor');
editor.setValue(window.sessionStorage.getItem(k + mdu));
// debugger;
// /shixuns/b5hjq9zm/challenges/3977/tab=3 setValue可能导致editor样式问题
md_clear_data(k, mdu, id);
}
}
window.md_rec_data = md_rec_data;
function md_elocalStorage(editor,mdu,id){
if (window.sessionStorage){
var oc = window.sessionStorage.getItem('content'+mdu);
if(oc !== null && oc != editor.getValue()){
console.log("#e_tips_"+id)
$("#e_tips_"+id).data('editor', editor);
var h = '您上次有已保存的数据,是否<a style="cursor: pointer;" class="link-color-blue" onclick="md_rec_data(\'content\',\''+ mdu + '\',\'' + id + '\')">恢复</a> ? / <a style="cursor: pointer;" class="link-color-blue" onclick="md_clear_data(\'content\',\''+ mdu + '\',\'' + id + '\')">不恢复</a>';
$("#e_tips_"+id).html(h);
}
setInterval(function() {
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var s = d.getSeconds();
h = h < 10 ? '0' + h : h;
m = m < 10 ? '0' + m : m;
s = s < 10 ? '0' + s : s;
if(editor.getValue().trim() != ""){
md_add_data("content",mdu,editor.getValue());
var id1 = "#e_tip_"+id;
var id2 = "#e_tips_"+id;
var textStart = " 数据已于 "
var text = textStart + h + ':' + m + ':' + s +" 保存 ";
// 占位符
var oldHtml = $(id2).html();
if (oldHtml && oldHtml != ' ' && oldHtml.startsWith(textStart) == false) {
$(id2).html( oldHtml.split(' (')[0] + ` (${text})`);
} else {
$(id2).html(text);
}
// $(id2).html("");
}
},10000);
}else{
$("#e_tip_"+id).after('您的浏览器不支持localStorage.无法开启自动保存草稿服务,请升级浏览器!');
}
}
function create_editorMD(id, width, high, placeholder, imageUrl, callback, initValue,
onchange, watch, { noStorage, showNullButton, emoji }, that) {
// 还是出现了setting只有一份被共用的问题
var editorName = window.editormd(id, {
width: width,
height: high===undefined?400:high,
path: path, // "/editormd/lib/"
markdown : initValue,
dialogLockScreen: false,
watch:watch===undefined?true:watch,
syncScrolling: "single",
tex: true,
tocm: true,
emoji: !!emoji ,
taskList: true,
codeFold: true,
searchReplace: true,
htmlDecode: "style,script,iframe",
sequenceDiagram: true,
autoFocus: false,
// mine
toolbarIcons: function (mdEditor) {
let react_id = `react_${mdEditor.id}`;
const __that = window[react_id]
// Or return editormd.toolbarModes[name]; // full, simple, mini
// Using "||" set icons align right.
const icons = ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "link", "|", "testIcon", "testIcon1", '|', "image", "table", '|', "watch", "clear"];
// 试卷处用到的填空题新增按钮
if (__that.props.showNullButton) {
icons.push('nullBtton')
}
return icons
},
toolbarCustomIcons: {
testIcon: "<a type=\"inline\" class=\"latex\" ><div class='zbg'></div></a>",
testIcon1: "<a type=\"latex\" class=\"latex\" ><div class='zbg_latex'></div></a>",
nullBtton: "<a type=\"nullBtton\" class='pr' title='增加填空'><div class='border-left'><span></span></div><span class='fillTip'>点击插入填空项</span><i class=\"iconfont icon-edit font-16\"></i></a>",
},
//这个配置在simple.html中并没有但是为了能够提交表单使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中方便post提交表单。
saveHTMLToTextarea: true,
// 用于增加自定义工具栏的功能可以直接插入HTML标签不使用默认的元素创建图标
dialogMaskOpacity: 0.6,
placeholder: placeholder,
imageUpload: true,
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL: imageUrl,//url
onchange: onchange,
onload: function() {
let _id = this.id // 如果要使用this这里不能使用箭头函数
let _editorName = this;
let react_id = `react_${_editorName.id}`;
const __that = window[react_id]
// this.previewing();
// let _id = id;
$("#" + _id + " [type=\"latex\"]").bind("click", function () {
_editorName.cm.replaceSelection("```latex");
_editorName.cm.replaceSelection("\n");
_editorName.cm.replaceSelection("\n");
_editorName.cm.replaceSelection("```");
var __Cursor = _editorName.cm.getDoc().getCursor();
_editorName.cm.setCursor(__Cursor.line - 1, 0);
});
$("#" + _id + " [type=\"inline\"]").bind("click", function () {
_editorName.cm.replaceSelection("`$$$$`");
var __Cursor = _editorName.cm.getDoc().getCursor();
_editorName.cm.setCursor(__Cursor.line, __Cursor.ch - 3);
_editorName.cm.focus();
});
$("[type=\"inline\"]").attr("title", "行内公式");
$("[type=\"latex\"]").attr("title", "多行公式");
if (__that.props.showNullButton) {
const NULL_CH = '▁'
// const NULL_CH = ''
// const NULL_CH = '🈳'
$("#" + _id + " [type=\"nullBtton\"]").bind("click", function () {
_editorName.cm.replaceSelection(NULL_CH);
// var __Cursor = _editorName.cm.getDoc().getCursor();
// _editorName.cm.setCursor(__Cursor.line - 1, 0);
});
}
if (noStorage == true) {
} else {
md_elocalStorage(_editorName, `MDEditor__${_id}`, _id);
}
callback && callback(_editorName)
}
});
return editorName;
}
export default class MDEditor extends Component {
constructor(props) {
super(props)
this.state = {
initValue: ''
}
}
componentDidUpdate(prevProps, prevState) {
// 不能加,影响了试卷填空题
// if (this.props.initValue != prevProps.initValue) {
// this.answers_editormd.setValue(this.props.initValue)
// }
}
// react_mdEditor_
componentDidMount = () => {
const { mdID, initValue, placeholder, showNullButton} = this.props;
let _id = `mdEditor_${mdID}`
this.contentChanged = false;
const _placeholder = placeholder || "";
// amp;
// 编辑时要传memoId
const imageUrl = `/upload_with_markdown?container_id=${mdID || ''}&container_type=Memo`;
// 创建editorMd
let react_id = `react_${_id}`;
// 将实例存到了window
window[react_id] = this
const answers_editormd = create_editorMD(_id, '100%', this.props.height, _placeholder, imageUrl, (_editorName) => {
const __editorName = _editorName;
react_id = `react_${__editorName.id}`;
const that = window[react_id]
// 一个延迟的recreate或resize不加这段代码md初始化可能会出现样式问题
setTimeout(() => {
if (that.props.needRecreate == true) {
__editorName.recreate() // 注意 必须在setValue之前触发不然会清空
} else {
__editorName.resize()
}
console.log('timeout', __editorName.id)
__editorName.cm && __editorName.cm.refresh()
}, that.props.refreshTimeout || 500)
if (this.props.noSetValueOnInit) {
that.onEditorChange()
} else {
if (that.props.initValue != undefined && that.props.initValue != '') {
__editorName.setValue(that.props.initValue)
}
if (that.state.initValue) {
__editorName.setValue(that.state.initValue)
}
}
__editorName.cm.on("change", (_cm, changeObj) => {
that.contentChanged = true;
if (that.state.showError) {
that.setState({showError: false})
}
that.onEditorChange()
})
that.props.onCMBlur && __editorName.cm.on('blur', () => {
that.props.onCMBlur()
})
that.props.onCMBeforeChange && __editorName.cm.on('beforeChange', (cm,change) => {
that.props.onCMBeforeChange(cm,change)
})
that.answers_editormd = __editorName;
// 这里应该可以去掉了,方便调试加的
window[__editorName.id+'_'] = __editorName;
}, initValue, this.onEditorChange,this.props.watch, {
noStorage: this.props.noStorage,
showNullButton: this.props.showNullButton,
emoji: this.props.emoji
}, this);
}
// 用在form里时validate失败时出现一个红色边框
showError = () => {
this.setState({showError: true})
}
onEditorChange = () => {
if (!this.answers_editormd) return;
const val = this.answers_editormd.getValue();
//console.log('onEditorChange', this.props.id, val)
try {
this.props.onChange && this.props.onChange(val)
} catch(e) {
// http://localhost:3007/courses/1309/common_homeworks/6566/setting
// 从这个页面,跳转到编辑页面,再在编辑页面点击返回的时候,这里会报错
console.error('出错')
console.error(e)
}
}
resize = () => {
if (!this.answers_editormd) { // 还未初始化
return;
}
this.answers_editormd.resize()
this.answers_editormd.cm && this.answers_editormd.cm.refresh()
this.answers_editormd.cm.focus()
}
getValue = () => {
try {
return this.answers_editormd.getValue()
} catch (e) {
return ''
}
}
setValue = (val) => {
try {
this.answers_editormd.setValue(val)
} catch (e) {
// TODO 这里多实例的时候前一个实例的state会被后面这个覆盖 参考NewWork.js http://localhost:3007/courses/1309/homework/9300/edit/1
// 未初始化
this.setState({ initValue: val })
}
}
render() {
let {
showError
} = this.state;
let { mdID, className, noStorage, imageExpand } = this.props;
let _style = {}
if (showError) {
_style.border = '1px solid red'
}
return (
<React.Fragment>
<div className={`df ${className} ${imageExpand && 'editormd-image-click-expand' }`} >
{/* padding10-20 */}
<div className="edu-back-greyf5 radius4" id={`mdEditor_${mdID}`} style={{..._style}}>
<textarea style={{display: 'none'}} id={`mdEditors_${mdID}`} name="content"></textarea>
<div className="CodeMirror cm-s-defualt">
</div>
</div>
</div>
{
noStorage == true ? '' :
<div className={"fr rememberTip"}>
<p id={`e_tips_mdEditor_${mdID}`} className="edu-txt-right color-grey-cd font-12"> </p>
{/* {noStorage == true ? ' ' : <p id={`e_tips_mdEditor_${mdID}`} className="edu-txt-right color-grey-cd font-12"> </p>} */}
</div>
}
</React.Fragment>
)
}
}

269
src/common/mdEditor.css Normal file
View File

@ -0,0 +1,269 @@
.CodeMirror-scroll {
overflow: auto !important;
margin-bottom: -30px;
margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none;
position: relative;
}
a.white-btn.orange-btn:hover {
border: 1px solid #F06200;
color: #FFF !important;
}
.flex1 a.white-btn.orange-btn:hover {
border: 1px solid #F06200;
color: #FFF !important;
}
/*.challenge_nav li a{*/
/*color:#000 !important;*/
/*}*/
.questionli{
width: 95%;
margin-left: 37px;
}
#directory_file{
height:200px;
overflow-y:auto;
background:#f5f5f5;
padding:10px;
}
.directory_filepath{
width:120px;
text-align:left;
}
a{
text-decoration: none;
color: #05101a;
}
.repository_url_tippostion{
position: absolute;
left: 22%;
width: 500px;
top: 100%;
}
.top-black-trangleft {
display: block;
border-width: 8px;
position: absolute;
top: -16px;
/* right: 4px; */
border-style: dashed solid dashed dashed;
border-color: transparent transparent rgba(5,16,26,0.6) transparent;
font-size: 0;
line-height: 0;
}
#exercisememoMD .CodeMirror {
margin-top: 31px !important;
height: 370px !important;
/*width: 579px !important;*/
}
#exercisememoMD .editormd-preview {
top: 40px !important;
height: 370px !important;
width: 578px !important;
}
#exercisememoMD{
/*height: 700px !important;*/
}
#questioMD{
/*width: 95% !important;*/
height: 417px !important;
margin-left: 0% !important;
}
#questioMD .CodeMirror {
/*width: 550.5px !important;*/
margin-top: 31px !important;
height: 374px !important;
}
#questioMD .editormd-preview {
top: 40px !important;
height: 375px !important;
width: 550px !important;
}
#newquestioMD .CodeMirror {
/*width: 549px !important;*/
margin-top: 31px !important;
height: 364px !important;
}
#newquestioMD .editormd-preview {
top: 40px !important;
height: 364px !important;
width: 578px !important;
}
#challenge_choose_answer .CodeMirror {
margin-top: 31px !important;
height: 364px !important;
/*width: 578px !important;*/
}
#challenge_choose_answer .editormd-preview {
top: 40px !important;
height: 364px !important;
width: 578px !important;
}
#neweditanswer .CodeMirror {
margin-top: 31px !important;
height: 364px !important;
/*width: 549.5px !important;*/
}
#neweditanswer .editormd-preview {
top: 40px !important;
height: 364px !important;
width: 551px !important;
}
#repository_url_tip {
top: 30px !important;
left: 249px !important;
width: 292px !important;
}
#editanswers .CodeMirror{
/*width: 548px !important;*/
height: 358px !important;
margin-top: 30px !important;
}
#editanswers .editormd-preview{
width: 578px !important;
height: 358px !important;
}
#newquestioMDs .CodeMirror{
/*width: 510px !important;*/
height: 358px !important;
margin-top: 30px !important;
}
#newquestioMDs .editormd-preview{
width: 578px !important;
height: 358px !important;
}
.choose_names{
width: 80px;
margin-left: 20px;
}
#answerMD .CodeMirror{
/*width: 569px !important;*/
height: 600px !important;
margin-top: 30px !important;
}
#answerMD .editormd-preview{
width: 578px !important;
height: 600px !important;
}
#answerMD {
height: 600px !important;
}
.textareavalue{
width: 100%;
padding: 5px;
box-sizing: border-box;
}
.greyInput{
width: 107%;
}
.greyInpus{
width: 100%;
}
.pdr20{
padding-right:20px;
}
.winput-240-40s {
background-color: #F5F5F5;
}
.winput-240-40s:focus{
background-color: #fff;
}
.input-100-45{
background-color: #F5F5F5;
}
.input-100-45:focus{
background-color: #fff;
}
.wind100{
width:100% !important;
}
.color-bule-tip {
color: #5485f7 !important;
}
.martopf4{
margin-top:-4px;
}
.headdfgf{
display: block;
width: 100px;
height: 30px;
line-height: 30px;
float: left;
}
.color979797{
color: #979797 !important;
}
.border-left{
width: 0;
height: 0;
border-bottom: 6px solid transparent;
border-right: 6px solid #cccbcb;
border-top: 6px solid transparent;
position: absolute;
left: 30px;
top: 12px;
}
.border-left span{
display: block;
width: 0;
height: 0;
border-bottom: 6px solid transparent;
border-right: 6px solid #fff;
border-top: 6px solid transparent;
position: absolute;
left: 1px;
top: -6px;
z-index: 10;
}
.fillTip{
position: absolute;
left: 36px;
top: 2px;
width: 125px;
font-size: 12px;
display: block;
padding: 5px;
border: 1px solid #eaeaea;
border-radius: 5px;
box-sizing: border-box;
height: 32px;
line-height: 20px;
font-family: "微软雅黑","宋体";
}

View File

@ -0,0 +1,40 @@
import React, { PureComponent } from 'react';
import { Popconfirm } from 'antd';
import './comment.css';
import RenderHtml from "../../components/render-html";
class CommentsItem extends PureComponent{
cancelEvent=()=>{}
render(){
const { username , time , content , id , admin , deleteReplyEvent } = this.props
// 当前用户是否是管理员或者版主true为有权限删除评论
const adminDelete = (
admin &&
<Popconfirm
title="确定删除这条评论?"
onConfirm={()=>deleteReplyEvent(id)}
onCancel={this.cancelEvent}
okText="确定"
cancelText="取消"
>
<a className="fr c_point color-grey-9">删除</a>
</Popconfirm>
)
return(
<React.Fragment>
<p className="mt3 mb10 clearfix">
<span className="color-grey3 font-16 mr20">{username}</span>
<span className="color-grey9">{time}</span>
{ adminDelete }
</p>
<RenderHtml className="forumsDetailHtml" value={content} />
</React.Fragment>
)
}
}
export default CommentsItem;

View File

@ -0,0 +1,17 @@
import React, { PureComponent } from 'react';
import { getImageUrl } from 'educoder';
import './comment.css';
class CommentsItemImg extends PureComponent{
render(){
const { image_url } = this.props
return(
<img alt="用户头像" src={getImageUrl(`images/${image_url}`)} width="64" height="64" className="radius mr20"></img>
)
}
}
export default CommentsItemImg;

View File

@ -0,0 +1,72 @@
import React, { Component } from 'react';
import MDEditor from '../../common/MDEditor';
import { Form , Button } from "antd";
import ItemImg from './CommentsItemImg'
import '../exchange.css'
import axios from 'axios';
class CommentsSend extends Component{
constructor(props){
super(props);
this.contentMdRef = React.createRef();
}
// 发送评论
handleSubmit=()=>{
this.props.form.validateFieldsAndScroll((err, values) => {
if(!err){
let { id , refresh } = this.props;
const url = `/memos/${id}/reply`;
axios.post(url,{
content:values.replyContent,
parent_id:id
}).then(result=>{
if(result){
this.contentMdRef.current.setValue(' ');
refresh();
}
}).catch(error=>{
})
}
})
}
render(){
const { getFieldDecorator } = this.props.form;
// 唯一键
const { unique , image_url } = this.props;
const imgWrap = (
image_url && <ItemImg image_url={image_url} ></ItemImg>
)
return(
<div className="df mt15">
{ imgWrap }
<div className="flex1">
<Form.Item label="" className="editorFromItem">
{getFieldDecorator('replyContent', {
rules: [{
required: true, message: '请输入评论内容',
},{
max: 5000 , message:'最大限制5000个字符'
}],
})(
<MDEditor ref={this.contentMdRef} placeholder="请输入评论内容最大限制5000字符" mdID={`replyContent${unique}`} refreshTimeout={1500}
className="CommentSendMD" height={150} watch={false} noStorage={true}></MDEditor>
)}
</Form.Item>
<p className="clearfix pb10 pt10">
<Button type="primary" onClick={this.handleSubmit} className="small-default-btn small-blue-btn fr">发送</Button>
</p>
</div>
</div>
)
}
}
const WrappedCommentsSendForm = Form.create({ name: 'CommentsSend' })(CommentsSend);
export default WrappedCommentsSendForm;

View File

View File

@ -0,0 +1,17 @@
import React, { PureComponent } from 'react';
import './custom.css'
class Index extends PureComponent {
render() {
let { img_url , name ,hrefUrl} = this.props;
return (
<span {...this.props}>
<a target="_blank" rel="noopener noreferrer" href={hrefUrl} ><img alt="用户头像" src={img_url} className="radius custom-img"></img></a>
<a target="_blank" rel="noopener noreferrer" href={hrefUrl} ><span className="wrap-name">{name}</span></a>
</span>
)
}
}
export default Index;

View File

@ -0,0 +1,34 @@
/* 论坛主页 */
.custom-wrap{
display: flex;
align-items: center
}
.custom-wrap .wrap-name{
color:#333;
font-size: 16px;
}
.custom-wrap .custom-img{
width:36px;
height:36px;
margin-right: 8px;
}
/* 版块主页 */
.moderatorInfo{
width: 82px;
text-align: center;
display: flex;
flex-direction:column;
align-items: center;
margin:10px 2px 20px 2px;
}
.moderatorInfo .custom-img{
height: 48px;
width: 48px;
}
.moderatorInfo .wrap-name{
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 80px;
}

15
src/exchange/Empty.js Normal file
View File

@ -0,0 +1,15 @@
import React, { PureComponent } from 'react';
import nodata from './images/nodata.png';
class Empty extends PureComponent{
render(){
return(
<div style={{textAlign:'center',fontSize:"20px"}}>
<img alt="暂无数据" src={nodata} style={{marginBottom:"20px"}}></img>
<p>暂无数据</p>
</div>
)
}
}
export default Empty;

View File

@ -0,0 +1,164 @@
import React, { Component } from 'react';
import { Menu, Dropdown , Pagination ,Spin} from 'antd';
import { Link } from 'react-router-dom'
import ExchangeItem from './ExchangeItem';
import ExchangeRight from './ExchangeRight'
import './exchange.css';
import Empty from './Empty'
import axios from 'axios';
class ExchangeIndex extends Component {
constructor(props){
super(props);
this.state={
// 热门话题
hottest_memos:undefined,
// 版主推荐
recommend_memos:undefined,
// 列表数据
memos:undefined,
// 列表总数量
memos_count:0,
// 板块导航
forum_sections:undefined,
page:1,
current_user:undefined,
// 默认一页数据
pageSize:15,
search:undefined,
//是否出现加载中的样式
loading:false
}
}
componentDidMount = () =>{
this.InitData();
}
// 数据加载
InitData=(page,search)=>{
let url = `/memos`;
axios.get((url),{params:{
page,search
}}).then((result)=>{
if(result){
this.setState({
loading:false,
hottest_memos:result.data.hottest_memos,
recommend_memos:result.data.recommend_memos,
forum_sections:result.data.forum_sections,
memos:result.data.memos,
memos_count:result.data.memos_count,
current_user:result.data.current_user
})
}
}).catch((error)=>{
})
}
// 搜索
searchEvent=(search)=>{
this.setState({
loading:true,
search,
page:1
})
this.InitData(0,search);
}
// 分页
changePageEvent = (pageNumber) =>{
this.setState({
page:pageNumber
})
const { search } = this.state;
this.InitData(pageNumber,search);
}
render(){
let { hottest_memos , recommend_memos , memos , memos_count , forum_sections , page , pageSize , current_user } = this.state;
// 板块导航dropdown显示内容
const menu = (
<div className="platePanel">
{
forum_sections && forum_sections.map(item => {
return(
<div className="plateItem">
<span className="plateItem_h"><Link to={`/forums/theme/${item.id}`}>{item.name}</Link></span>
<ul className="plateUl">
{
item.children_tags && item.children_tags.map(i=>{
return(<li><Link to={`/forums/theme/${i.id}`}>{i.title}</Link></li>)
})
}
</ul>
</div>
)
})
}
</div>
);
// 数据列表和暂无数据
const dataList = memos && memos.length > 0 ?
<ExchangeItem memos = {memos} {...this.props} {...this.state} refresh={this.InitData} current_user={current_user} page={page}/>
:
<div className="pt50 pb50"><Empty /></div>
return(
<div className="clearfix F_panel">
<div className="fl with76 pr20">
<div className="back-color-white">
<div className="f_left_head">
<ul>
<li className="active">
<a>论坛首页</a>
</li>
{ forum_sections &&
<li>
<Dropdown overlay={menu}>
<a>板块导航<i className="iconfont icon-xiajiantou font-16 ml10 color-dark-grey"></i></a>
</Dropdown>
</li>
}
</ul>
<ul>
<li><Link to='/forums/MyTopic'>我的话题</Link></li>
<li><Link to='/forums/MyEnshrine'>我的收藏</Link></li>
<li><Link to='/forums/MyInteresting'>我感兴趣的论坛</Link></li>
</ul>
</div>
<Spin spinning={this.state.loading} >
{ dataList }
</Spin>
</div>
{
memos_count > pageSize &&
<div className="pt30 pb50 edu-txt-center"><Pagination showQuickJumper current={page} total={memos_count} onChange={this.changePageEvent} pageSize={pageSize} /></div>
}
</div>
<ExchangeRight
{...this.props}
{...this.state}
searchEvent={this.searchEvent}
hottest_memos={hottest_memos}
recommend_memos={recommend_memos}
loading={this.setState.loading}
/>
</div>
)
}
}
export default ExchangeIndex;

View File

@ -0,0 +1,198 @@
import React, { Component, memo } from 'react';
import { Dropdown , Menu } from 'antd';
import {Link} from 'react-router-dom'
import { getImageUrl } from 'educoder';
import Tags from './TagComponent/Index';
import Infos from './InfoComponent/Index';
import Custom from './CustomComponent/Index';
import "./exchange.css"
import axios from 'axios';
class ExchangeItem extends Component {
// 右侧dropdown
InitItemMenu = (id,sticky,is_fine,memo_watched) => {
const { current_user , detail }= this.props;
if(current_user){
if(current_user.admin || current_user.banned_permission){
return(
<Dropdown className="fr c_point" overlay={ this.InitlistMenu(id,sticky,is_fine,memo_watched) } placement="bottomCenter">
<span className="addheight"></span>
</Dropdown>
)
}else if(detail){
return(
<span className="fr c_point color-blue" style={{lineHeight:'16px'}} onClick={()=>this.enShrineEvent(id,memo_watched)}>
{ memo_watched ? '取消收藏' : '收藏' }
</span>
)
}
}
}
// 右侧dropdown的选项
InitlistMenu = (id,sticky,is_fine,memo_watched) => (
<Menu className="edu-txt-center" style={{minWidth:"100px"}}>
<Menu.Item onClick={()=>this.topEvent(sticky,id)} > { sticky ? '取消置顶' : '置顶' } </Menu.Item>
<Menu.Item onClick={()=>this.bestEvent(is_fine,id)}> { is_fine ? '取消推荐' : '推荐' } </Menu.Item>
<Menu.Item onClick={()=>this.enShrineEvent(id,memo_watched)}> { memo_watched ? '取消收藏' : '收藏' } </Menu.Item>
<Menu.Item onClick={()=>this.editEvent(id)}> 编辑 </Menu.Item>
<Menu.Item onClick={()=>this.deleteEvent(id)}> 删除 </Menu.Item>
</Menu>
)
// 取消收藏+收藏
enShrineEvent=(id,memo_watched)=>{
const { refresh } = this.props;
const url = `/memos/${id}/watch_memo`;
// is_watch:1为添加关注
axios.post(url,{
is_watch:memo_watched ? 0 : 1
}).then((result)=>{
if(result){
this.props.showNotification(result.data.message);
refresh();
}
}).catch((error)=>{
})
}
// 取消置顶+置顶
topEvent = (sticky,id) =>{
const { refresh } = this.props;
const url =`/memos/${id}/set_top_or_down.json`;
axios.get(url,{params:{
sticky:sticky ? 0 : 1
}}).then((result)=>{
if(result){
// 调用父级方法刷新
this.props.showNotification(result.data.message);
refresh();
}
}).catch((error)=>{
})
}
// 取消推荐+推荐
bestEvent = (is_fine,id) =>{
const { refresh } = this.props;
const url =`/memos/${id}/is_fine.json`;
axios.post(url,{
is_fine:is_fine ? 0 : 1
}).then((result)=>{
if(result){
// 调用父级方法刷新
this.props.showNotification(result.data.message);
refresh();
}
}).catch((error)=>{
})
}
// 编辑
editEvent=(id)=>{
this.props.history.push(`/forums/${id}/edit`);
}
// 删除
deleteEvent = (id) =>{
this.props.confirm({
content: '确认删除帖子?',
onOk: () => {
const url =`/memos/${id}/destroy.json`;
axios.post(url).then((result)=>{
if(result){
if(result.data.status === 0){
this.props.showNotification(result.data.message);
const { page , refresh } = this.props;
if(page){
refresh(page);
}else{
this.props.history.push("/forums");
}
}
}
}).catch((error)=>{
})
},
onCancel() {
console.log('Cancel');
},
});
}
// 跳转页面
turnToEvent=(id,detail)=>{
{/*传了enshine就代表底部右侧操作按钮只有收藏或者取消收藏否则就是置顶、推荐等 */}
const { enShrine} = this.props;
if(enShrine){return}
if(detail!=true){
const w= window.open('about:blank');
w.location.href=`/forums/${id}`;
}
}
render(){
/**detail:true为详情否则为列表 */
const { memos , current_user , detail} = this.props;
console.log(memos)
const ListItem = memos && memos.map(item=>{
return(
<li>
<div>
<div className="tabulation_infos flex-align-top mb15 ">
<Tags bestClass={item.is_fine ? "mr15" : undefined } topClass={item.sticky ? "mr15" : undefined}></Tags>
<p onClick={()=>this.turnToEvent(item.id,detail)} className={ detail ? "exchangeItem-subject task-hide justify color_black" : "exchangeItem-subject task-hide justify c_point is_onclick" }>
{item.subject}
</p>
{/* 右侧的下拉操作菜单项:登录后才能有右侧信息 */}
{ current_user && this.InitItemMenu(item.id,item.sticky,item.is_fine,item.memo_watched) }
</div>
</div>
<div className="flex-align-center">
{/* 判断用户是否登录 */}
<Custom className="custom-wrap fl" hrefUrl={`/users/${item.user_login}`} img_url = {getImageUrl(`images/${item.image_url}`)} name={item.username}></Custom>
{ item.forum_section_title && <span className="sendPoint">发表在<Link className="color-blue c_point" to={`/forums/theme/${item.forum_section_id}`}>{item.forum_section_title}</Link></span> }
{ item.time && <span className="sendPoint">{item.time}</span> }
{
item.new_reply && item.new_reply.username &&
<span className="fl ml30 pl10 sendPoint">
<span className="color-grey-9 font-14">最新回复
<span className="color-grey-3">{item.new_reply.username}</span>
<span className="ml10 font-12">{item.new_reply.time}</span>
</span>
</span>
}
<span className="flex1"></span>
{/* 论坛首页和论坛详情公用,不同情况下左浮动或者有浮动 */}
<span className="fr">
<Infos className="icon-wrap" icon={"iconfont icon-liulan font-16"} count={item.viewed_count} />
<Infos className="icon-wrap" icon={"iconfont icon-dianzan font-16"} count={item.praises_count} />
<Infos className="icon-wrap" icon={"iconfont icon-pinglun font-16"} count={item.replies_count} />
</span>
</div>
</li>
)
})
return(
<div className="plateTabulation">
{ ListItem }
</div>
)
}
}
export default ExchangeItem;

View File

@ -0,0 +1,68 @@
import React, { PureComponent } from 'react';
import { Link } from "react-router-dom"
import { getImageUrl } from 'educoder';
import ExchangeRightSearch from './ExchangeRightSearch'
import './exchange.css'
class ExchangeRight extends PureComponent {
render(){
const { searchEvent , hottest_memos , recommend_memos , hideSearchPanel} = this.props;
// 热门话题
const hotList = hottest_memos && hottest_memos.length > 0 && (
<div className="bc-white mb20">
<p className="clearfix r_part_title">
<img src={getImageUrl("images/plate/hot.png")} width="13px" className="mr10 fl mt7" alt=""/>
<span className="color-grey3 font-16 fl">热门话题</span>
</p>
<ul className="r_part_list">
{
hottest_memos.map((apply)=>{
return(
<li><a target="_blank" href={`/forums/${apply.id}`}>{apply.subject}</a></li>
)
})
}
</ul>
</div>
)
// 版主推荐
const moderatorList = recommend_memos && recommend_memos.length > 0 && (
<div className="bc-white">
<p className="clearfix r_part_title">
<img src={getImageUrl("images/plate/point.png")} width="16px" className="mr10 fl mt7" alt=""/>
<span className="color-grey3 font-16 fl">版主推荐</span>
</p>
<ul className="r_part_list">
{
recommend_memos.map((apply)=>{
return(
<li><a target="_blank" href={`/forums/${apply.id}`}>{apply.subject}</a></li>
)
})
}
</ul>
</div>
)
return(
<div className="fl with24">
{
!hideSearchPanel && <ExchangeRightSearch {...this.props} {...this.state} searchEvent={searchEvent}></ExchangeRightSearch>
}
{/* 热门话题 */}
{hotList}
{/* 版主推荐 */}
{ moderatorList }
</div>
)
}
}
export default ExchangeRight;

View File

@ -0,0 +1,64 @@
import React, { Component } from 'react';
import { Input } from 'antd'
import { Link } from 'react-router-dom'
const Search = Input.Search;
class ExchangeRightSearch extends Component {
constructor(props) {
super(props)
this.state={
searchDefault:true
}
}
// 有关搜索部分
activeSearch =(e)=>{
this.props.searchEvent(e);
if(!e){
this.setState({
searchDefault:true
})
}
}
showSearchPanel = () =>{
this.setState({
searchDefault:false
})
}
render(){
let { searchDefault } = this.state;
const { current_user } = this.props;
const sendBtn =()=> {
if(current_user){
return(<Link to={'/forums/new'} className="send_btn">发布话题</Link>)
}else{
return(<a href="/login" className="send_btn">发布话题</a>)
}
}
return(
<div className="top_Operate">
{sendBtn()}
{
searchDefault ?
<Search
className="searchfrom"
placeholder="请输入您想搜索的内容"
size="large"
onSearch={this.showSearchPanel}
/>
:
<Search
className="searchfrom active"
placeholder="请输入您想搜索的内容"
size="large"
onSearch={this.activeSearch}
/>
}
</div>
)
}
}
export default ExchangeRightSearch;

View File

@ -0,0 +1,19 @@
import React, { PureComponent } from 'react';
class Index extends PureComponent {
render() {
const { icon , count = 0 , ...props } = this.props;
return (
<span {...props}>
<i className={icon}></i>
<span className={'span-text'}>
{count}
</span>
</span>
)
}
}
export default Index;

View File

@ -0,0 +1,171 @@
import React, { Component } from 'react';
import { Modal , Input , Table ,Pagination } from 'antd'
import './manage.css'
import '../exchange.css';
import axios from 'axios';
const Search = Input.Search;
class AddModeratorModal extends Component {
constructor(props){
super(props);
this.state={
data:undefined,
page:1,
user_name:undefined,
limit:10,
total:undefined,
selectedRowKeys:undefined
}
}
componentDidUpdate=(prevState)=>{
if(prevState.operationPlateId !== this.props.operationPlateId && this.props.visible){
this.setState({
user_name:undefined,
page:1
})
this.getTabData(undefined , 1);
}
}
getTabData=(user_name,page)=>{
const { operationPlateId } = this.props;
const url =`/forum_sections/${operationPlateId}/search_users.json`;
axios.get(url,{
params:{
user_name,
page
}
}).then(result=>{
if(result){
const user_lists = result.data.user_lists;
let array = [];
for(var i = 0;i<user_lists.length;i++){
array.push({
key:user_lists[i].id,
username:user_lists[i].username,
nickname:user_lists[i].nickname,
})
}
this.setState({
data:array,
limit:result.data.limit,
total:result.data.users_count
})
}
}).catch(error=>{
})
}
getSelectKeys=(selectedRowKeys,selectedRows)=>{
this.setState({
selectedRowKeys
})
}
// 搜索
searchEvent=(value)=>{
this.setState({
user_name:value,
page:1
})
this.getTabData(value,1);
}
// 翻页
changePageEvent=(page)=>{
const { user_name } = this.state;
this.setState({
page
})
this.getTabData(user_name,page);
}
// 取消
cancel=()=>{
const { hideAddBox }=this.props;
this.setState({
user_name:undefined,
page:1
})
hideAddBox();
}
// 确定
modalSave=()=>{
const { selectedRowKeys } = this.state;
console.log(selectedRowKeys);
const { plateId } = this.props.match.params;
const { operationPlateId , hideAddBox , getSubModerator } = this.props;
const url = `/forum_sections/${plateId}/add_users.json`;
axios.post(url,{
user_ids:selectedRowKeys,
children_section_id:operationPlateId
}).then(result=>{
if(result){
this.props.showNotification(result.data.message);
hideAddBox();
getSubModerator();
}
}).catch(error=>{
})
}
render(){
const { visible } = this.props;
let { data , total , page , limit } = this.state;
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => this.getSelectKeys(selectedRowKeys,selectedRows)
};
const columns = [{
title:"姓名",
dataIndex:"username"
},{
title:"昵称",
dataIndex:"nickname"
}];
return(
<Modal
keyboard={false}
title={"添加版主"}
visible={ visible }
closable={false}
footer={null}
destroyOnClose={true}
centered={true}
width="700px"
className="addPlateModal"
>
<div>
<div className="edu-txt-right clearfix mb20 pr30">
<div style={{width:"400px"}} className="fr">
<Search
placeholder="请输入用户名进行搜索"
enterButton="搜索"
onSearch={(value) => this.searchEvent(value)}
/>
</div>
</div>
<Table rowSelection={rowSelection} columns={columns} dataSource={data} size={"small"} pagination={false}/>
{
total && total > limit ?
<div className="edu-txt-center mt10">
<Pagination current={page} size={"small"} total={total} pageSize={limit} onChange={this.changePageEvent}></Pagination>
</div>:""
}
<div className="clearfix mt30 edu-txt-center">
<a className="task-btn mr30" onClick={this.cancel}>取消</a>
<a className="task-btn task-btn-orange" onClick={this.modalSave}>确定</a>
</div>
</div>
</Modal>
)
}
}
export default AddModeratorModal;

View File

@ -0,0 +1,54 @@
import React, { Component } from 'react';
import MenuWraps from '../MenuComponent/Menu';
import SendItem from './SubSendItem';
import CheckItem from './SubCheckItem'
import CheckReplyItem from './SubCheckReplyItem'
const menu_nav = [
{
name:"待审查帖子",
key:`checkPost`,
content:CheckItem
},
{
name:"待审查回复",
key:`checkReply`,
content:CheckReplyItem
},
{
name:"已发布的帖子",
key:`sendPost`,
content:SendItem
}
]
class CheckPublic extends Component {
constructor(props){
super(props);
this.state={
activeKey:undefined
}
}
changeTab=(activeKey)=>{
this.setState({
activeKey
})
}
render(){
return(
<div className="edu-back-white mb20">
<MenuWraps
{...this.props}
{...this.state}
className="plate-left-Menu moderatorMenu"
menu_nav={menu_nav}
defaultUrlKey={"checkPost"}
changeTab={this.changeTab}/>
</div>
)
}
}
export default CheckPublic;

View File

@ -0,0 +1,40 @@
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import { getImageUrl } from 'educoder';
import RenderHtml from "../../components/render-html";
import Customers from '../CustomComponent/Index'
import "./manage.css"
class ItemLeft extends Component {
render(){
// user_url,username , time , image_url :用户链接,用户名,时间,头像,
// memo_title , forum_title待审查帖子以及已发布帖子帖子名称发表的论坛名
// source_title , reply_content待审查回复帖子来源回复内容
//memo_id,forum_id,source_id 待审查帖子id发表在版块id,来源版块id
const {user_url,id, memo_id,username , time , image_url , memo_title , forum_title ,source_title , reply_content,forum_id,source_id} = this.props;
const title_Url= id || memo_id;
return(
<div className="flex1">
<p className="flex-align-center mb15">
<Customers className="ItemsHeadPhoto flex-align-center" hrefUrl={user_url} img_url={getImageUrl(`images/${image_url}`)} name={username}></Customers>
<span className="sendPoint">{time}</span>
{ forum_title && <span className="sendPoint">发表在 <Link to={`/forums/theme/${forum_id}`}><span className="green">{forum_title}</span></Link></span> }
{ source_title && <span className="sendPoint">来源 <Link to={`/forums/theme/${source_id}`}><span className="green">{source_title}</span></Link></span> }
</p>
{ memo_title && <p><Link to={`/forums/${title_Url}`}>{memo_title}</Link></p>}
{
reply_content &&
<Link to={`/forums/${title_Url}/detail`}>
<RenderHtml className="reply_manage_content" value={reply_content} />
</Link>
}
</div>
)
}
}
export default ItemLeft;

View File

@ -0,0 +1,87 @@
import React, { Component } from 'react';
import { Dropdown } from 'antd';
import { Link } from 'react-router-dom';
import Nav from '../NavComponent/Index';
class ModeratorNav extends Component {
render(){
const { current_user , bread_crumb }=this.props;
const forum_tag = bread_crumb && bread_crumb.forum_tag;
const forum = bread_crumb && bread_crumb.forum;
// {
// name:current_user && current_user.username,
// url:`/users/${current_user && current_user.login}`
// },
const routerMap = [
{
name: forum && forum.title,
url:"/forums"
},
{
url:`/forums/theme/${forum_tag && forum_tag.id}`,
name:forum_tag && forum_tag.title
},
{
url:`/forums/theme/${forum_tag && forum_tag.children_bread_crumb && forum_tag.children_bread_crumb.id}`,
name:forum_tag && forum_tag.children_bread_crumb && forum_tag.children_bread_crumb.title
},{
name:"版主管理"
}
];
// 板块导航dropdown显示内容
const menu =(item)=> {
// console.log("item",item);
if(item){
return(
<div className="platePanel">
<div className="plateItem">
<ul className="plateUl ">
{
item.map((i,key)=>{
return(<li key={key}><Link to={`/forums/manage/${i.id}`}>{i.title}</Link></li>)
})
}
</ul>
</div>
</div>
)
}
}
const titleFlag = () => {
if (forum_tag ) {
console.log(forum_tag.children_bread_crumb);
if (bread_crumb && bread_crumb.is_children) {
return (<div className="padding20-30 edu-back-white font-22 mb20">{forum_tag.children_bread_crumb && forum_tag.children_bread_crumb.title}</div>)
} else {
return (
<div className="padding20-30 edu-back-white font-22 mb20">
{ forum_tag.children_bread_crumb ?
<Dropdown overlay={menu(forum_tag.children_bread_crumb) } >
<div>{forum_tag.title}<i className="iconfont icon-xiajiantou font-16 ml10 color-dark-grey"></i></div>
</Dropdown>
:
<span>{forum_tag.title}</span>
}
</div>
)
}
}
}
return(
<div>
<Nav className="mt20 mb30" NavMap = {routerMap} ></Nav>
{titleFlag()}
</div>
)
}
}
export default ModeratorNav;

View File

@ -0,0 +1,35 @@
import React, { Component } from 'react';
import '../exchange.css';
import './manage.css';
import axios from 'axios'
class PassItem extends Component {
passEvent=(checked)=>{
const { id , refresh ,page } = this.props;
const url = `/memos/${id}/memo_hidden`;
axios.post(url,{
checked
}).then((result)=>{
if(result){
this.props.showNotification(result.data.message);
refresh(page);
}
})
}
render(){
return(
<p className="ml50">
<span className="middle-default-btn small-blue-btn c_point" onClick={()=>this.passEvent(true)}>通过</span>
<span className="middle-default-btn ml20 c_point" onClick={()=>this.passEvent(false)}>不通过</span>
</p>
)
}
}
export default PassItem;

View File

@ -0,0 +1,122 @@
import React, { Component } from 'react';
import { getImageUrl } from 'educoder';
import axios from 'axios';
import './manage.css'
import { Link } from 'react-router-dom';
class PreApplyPlate extends Component {
constructor(props){
super(props);
this.state={
applylist:undefined
}
}
componentDidMount=()=>{
const { plateId } = this.props.match.params;
this.getApplyInfo(plateId);
}
getApplyInfo=(id)=>{
const url=`/forum_sections/${id}/applied_forums`;
axios.get(url).then((result)=>{
if(result){
this.setState({
applylist:result.data.applied_moderators
})
}
}).catch(error=>{
})
}
// 通过,拒绝
passApplyEvent=(flag,applyId)=>{
this.props.confirm({
content: `确认${flag?"通过":"拒绝"}版主申请?`,
onOk: () => {
const { plateId } = this.props.match.params;
const url = `/forum_sections/${plateId}/deal_applies/${applyId}`
axios.post(url,{
deal_type:flag?1:2
}).then((result)=>{
if(result){
this.props.showNotification(result.data.message);
this.getApplyInfo(plateId);
}
}).catch((error)=>{
})
},
onCancel() {
console.log('Cancel');
}
})
}
render(){
const { applylist } = this.state;
// const applylist = [
// {
// apply_id: 5,
// username: "OpenGCC",
// login: "innov",
// image_url: "avatars/User/girl.jpg",
// user_url: "/users/innov",
// user_ip: null,
// user_ip_address: "--",
// time: "14小时前",
// forum_title: "MAC安全",
// forum_id: 9,
// forum_url: "/memos/forum_memos/9",
// parent_forum: {
// forum_title: "网络安全网络安全333",
// forum_id: 6,
// forum_url: "/memos/forum_memos/6"
// }
// }
// ]
const listItem= () => {
if(applylist && applylist.length > 0){
return(
<div className="mt20">
<p className="font-16 mb15 color-grey3">版主申请</p>
<div className="applyList">
{ applylist.map((item,key)=>{
return(
<div>
<a href={`/users/${item.login}`} > <img alt="用户头像" src={getImageUrl(`images/${item.image_url}`)} width="36" height="36" className="radius mr15"></img></a>
<div className="flex1">
<p className="mb10">
<a href={`/users/${item.login}`} > <span className="color-blue mr15">{item.username}</span></a>
{ item.user_ip && <span className="color-grey9">IP{item.user_ip}{item.user_ip_address}</span> }
</p>
<p>申请成为<Link to={`/forums/theme/${item.forum_id}`}><span className="color-blue">{item.forum_title}</span></Link></p>
</div>
<div className="edu-txt-right">
<p className="color-grey9 mb10">{item.time}</p>
<p>
<span className="middle-default-btn small-blue-btn c_point" onClick={()=>this.passApplyEvent(true,item.apply_id)}>通过</span>
<span className="middle-default-btn ml20 c_point" onClick={()=>this.passApplyEvent(false,item.apply_id)}>拒绝</span>
</p>
</div>
</div>
)
})
}
</div>
</div>
)
}
}
return(
<React.Fragment>
{listItem()}
</React.Fragment>
)
}
}
export default PreApplyPlate;

View File

@ -0,0 +1,88 @@
import React, { Component } from 'react';
import { Modal , Form , Input } from 'antd';
import '../exchange.css';
import axios from 'axios';
class PreCreate extends Component {
componentDidUpdate=(preState)=>{
const { subId , subName } = this.props;
if(preState.subId !== subId){
if(subId){
this.props.form.setFieldsValue({
title:subName
})
}
}
}
// 确定
modalSave=()=>{
this.props.form.validateFieldsAndScroll((err, values) => {
if(!err){
// subId存在就是编辑否则就是新增
const { refresh , subId } = this.props;
const { plateId } = this.props.match.params;
const url = `/forum_sections${subId ? "/rename":""}.json`;
axios.post(url,{
title:values.title,
children_section_id:subId,
id: plateId
}).then((result)=>{
if(result){
refresh();
this.props.showNotification(result.data.message);
}
}).catch((error)=>{
})
}
})
}
modalCancel=()=>{
this.props.close();
}
render(){
const { getFieldDecorator } = this.props.form;
const { title , visible } = this.props
return(
<Modal
keyboard={false}
title={title}
visible={ visible }
closable={false}
footer={null}
destroyOnClose={true}
centered={true}
width="530px"
>
<div className="task-popup-content">
<Form className="formInline">
<Form.Item
label="标题"
>
{getFieldDecorator('title', {
rules: [{
required: true, message: '请输入板块名称',
},{
max: 5000 , message:'最大限制20个字符'
}],
})(
<Input placeholder="请输入名称最大限制20个字符" maxLength="60" style={{height:"40px"}}/>
)}
</Form.Item>
<div className="clearfix mt30 edu-txt-center">
<a className="task-btn mr30" onClick={this.modalCancel}>取消</a>
<a className="task-btn task-btn-orange" onClick={this.modalSave}>确定</a>
</div>
</Form>
</div>
</Modal>
)
}
}
const WrappedPreCreate = Form.create({ name: 'PreCreate' })(PreCreate);
export default WrappedPreCreate;

View File

@ -0,0 +1,76 @@
import React, { Component } from 'react';
import CheckPublic from './CheckPublic'
import ModeratorNav from './ModeratorNav';
import ApplyPlate from './PreApplyPlate';
import PrePlateManage from './PrePlateManage'
import axios from 'axios';
class PreModerator extends Component {
constructor(props){
super(props);
this.state={
activeKey:undefined,
bread_crumb:undefined,
is_children:false,
}
}
changeTab=(activeKey)=>{
this.setState({
activeKey
})
}
componentDidMount=()=>{
const { plateId } = this.props.match.params;
this.getPlateInfo(plateId);
}
componentDidUpdate=(prevState)=>{
let prePlateId = prevState.match.params.plateId;
const { plateId } = this.props.match.params;
if(prePlateId !== prePlateId){
this.getPlateInfo(plateId);
}
}
getPlateInfo=(plateId)=>{
const url = `/forum_sections/${plateId}/forum_section_header`;
axios.get(url).then((result)=>{
if(result){
this.setState({
bread_crumb:result.data.bread_crumb,
is_children:result.data.bread_crumb && result.data.bread_crumb.is_children
})
}
}).catch(error=>{
})
}
render(){
const { bread_crumb , is_children } = this.state;
const parentManage =()=> {
if(!is_children){
return(
<React.Fragment>
<PrePlateManage {...this.props} {...this.state} getPlateInfo={this.getPlateInfo}/>
{/* 板块申请 */}
<ApplyPlate {...this.props} {...this.state}/>
</React.Fragment>
)
}
}
return(
<div className="newMain">
<div className="educontent">
<ModeratorNav {...this.props} {...this.state}/>
{parentManage()}
{/* 审批(二级版主只有这一块) */}
<CheckPublic {...this.props} {...this.state} bread_crumb={bread_crumb}/>
</div>
</div>
)
}
}
export default PreModerator;

View File

@ -0,0 +1,286 @@
import React, { Component } from 'react';
import { getImageUrl} from 'educoder';
import PreCreate from './PreCreate';
import './manage.css'
import '../exchange.css'
import {Link} from 'react-router-dom'
import axios from 'axios';
import update from 'immutability-helper'
import AddModeratorModal from './AddModeratorModal'
class PrePlateManage extends Component {
constructor(props){
super(props);
this.state={
visible:false,
children_tags:undefined,
subId:undefined,
subName:undefined,
// 新增版主有关
addVisible:undefined,
operationPlateId:undefined
}
}
componentDidMount=()=>{
this.getSubModerator();
}
// 获取所有二级板块
getSubModerator=()=>{
const { plateId } = this.props.match.params;
const url = `/forum_sections/${plateId}/managements`;
axios.get(url).then((result)=>{
if(result){
this.setState({
children_tags:result.data.forum_tag && result.data.forum_tag.children_tags
})
}
}).catch((error)=>{
})
}
// 新建板块和重命名板块后的刷新方法
reSetInfo=()=>{
this.setState({
visible:false,
subId:undefined,
subName:undefined
})
this.getSubModerator();
const { plateId } = this.props.match.params;
const { getPlateInfo } = this.props;
getPlateInfo(plateId);
}
// 展开
expandEvent=(index,flag)=>{
this.setState(
(prevState) => ({
children_tags : update(prevState.children_tags, {[index]: { expand: {$set: flag} }}),
})
)
}
// 显示删除版主的按钮
deleteManageEvent=(index,flag)=>{
console.log(index)
this.setState(
(prevState) => ({
children_tags : update(prevState.children_tags, {[index]: { isDeleting: {$set: flag} }}),
})
)
}
// 新建二级板块
createSubPlateEvent=()=>{
this.setState({
visible:true,
subId:undefined,
subName:undefined
})
}
// 二级板块重命名
RenamneSubPlateEvent=(id,name)=>{
this.setState({
visible:true,
subId:id,
subName:name
})
}
// 删除二级板块
DeleteSubPlateEvent=(id)=>{
this.props.confirm({
content: '确认删除二级板块?',
onOk: () => {
const { plateId } = this.props.match.params;
const url=`/forum_sections/destroy_forum.json`
axios.post(url,{
children_section_id:id,
id: plateId
}).then(result=>{
if(result){
this.props.showNotification(result.data.message);
this.getSubModerator();
const { getPlateInfo } = this.props;
getPlateInfo(plateId);
}
}).catch((error)=>{
})
},
onCancel() {
console.log('Cancel');
}
})
}
// 关闭新建弹框
colseModalEvent=()=>{
this.setState({
visible:false
})
}
// 删除版主
deletePlateEvent=(id,name,index,key)=>{
console.log(index,'dddd',key);
this.props.confirm({
content: `是否确认解除“${name}”的二级版主权限?`,
onOk: () => {
const { plateId } = this.props.match.params;
const url=`/forum_sections/${plateId}/destroy_moderator/${id}`
axios.post(url).then(result=>{
if(result){
this.props.showNotification(result.data.message);
const { children_tags } = this.state;
// 去掉当前选中的版主
let tempObj = children_tags[index].forum_moderators;
tempObj = tempObj.filter((_, i) => i !== key) ;
children_tags[index].forum_moderators = tempObj;
this.setState({ children_tags })
}
}).catch((error)=>{
})
},
onCancel() {
console.log('Cancel');
}
})
}
// 新增版主弹框
showAddBox=(flag,plateId)=>{
this.setState({
addVisible:true,
operationPlateId:plateId
})
}
hideAddBox=()=>{
this.setState({
addVisible:false,
operationPlateId:undefined
})
}
render(){
const { visible ,children_tags , subId , subName , addVisible , operationPlateId } = this.state;
// console.log("child",children_tags);
const forumTagList = ()=>{
if(children_tags && children_tags.length>0){
return(
<div className="subPlateList">
{
children_tags.map((item,key)=>{
return(
<div>
<div className={item.expand ? "subPlateItem active":"subPlateItem"}>
<p className="subPlateItem_head">
<span>{item.title}</span>
<span>
<span className="c_point mr10 color-green font-12" onClick={()=>this.RenamneSubPlateEvent(item.id,item.title)}>重命名</span>
<span className="c_point color-grey-9 sendPoint pl10" onClick={()=>this.DeleteSubPlateEvent(item.id)}>删除板块</span>
</span>
</p>
<p className="mt10 mb10 color-grey3">二级版主</p>
<div className="plateManager">
{ renderSubList(item.forum_moderators,key,item.isDeleting) }
<span className="fr">
{/* 展开 */}
{
item.forum_moderators && item.forum_moderators.length > 5 && !item.expand &&
<span className="c_point mr30" onClick={()=>this.expandEvent(key,true)}><i className="iconfont icon-gengduo1 font-36 color-grey-9"></i></span>
}
{/* 收起 */}
{
item.forum_moderators && item.forum_moderators.length > 5 && item.expand &&
<span className="c_point mr30" onClick={()=>this.expandEvent(key,false)}><i className="iconfont icon-shangjiantou-tianchong font-36 color-grey-9"></i></span>
}
{/* 新增版主 */}
<span className="c_point mr30" onClick={()=>this.showAddBox(true,item.id)}><i className="iconfont icon-roundaddfill color-green font-36"></i></span>
{/* 删除版主 */}
{
item.forum_moderators && item.forum_moderators.length > 0 && !item.isDeleting &&
<span className="c_point mr30" onClick={()=>this.deleteManageEvent(key,true)}><i className="iconfont icon-default color-grey-9 font-36"></i></span>
}
{
item.isDeleting && <span className="c_point mr30 completeIcon" onClick={()=>this.deleteManageEvent(key,false)}>完成</span>
}
</span>
</div>
</div>
</div>
)
})
}
</div>
)
}
}
const renderSubList = (item,index,deletingFlag) =>{
if(item){
return(
<ul>
{
item.map((i,key)=>{
return(
<React.Fragment>
{
!i.isDelete &&
<li>
<a href={`/users/${i.login}`}><img alt="" src={getImageUrl(`images/${i.image_url}`)} width="39" height="39" className="radius mb3"/></a>
{
deletingFlag &&
<i className="iconfont icon-htmal5icon19 deletePlateIcon" onClick={()=>this.deletePlateEvent(`${i.moderator_id}`,`${i.username}`,index,key)}></i>
}
<a href={`/users/${i.login}`}><span className="task-hide" style={{width:"39px",display:"block"}}>{i.username}</span></a>
</li>
}
</React.Fragment>
)
})
}
</ul>
)
}
}
return(
<div>
<div className="padding20-30 edu-back-white font-22 mb20 clearfix">
<PreCreate
{...this.props}
{...this.state}
visible={visible}
title="新建"
refresh={this.reSetInfo}
close={this.colseModalEvent}
subId={subId}
subName={subName}
/>
<AddModeratorModal
{...this.props}
{...this.state}
visible = {addVisible}
operationPlateId={operationPlateId}
hideAddBox={this.hideAddBox}
getSubModerator={this.getSubModerator}
/>
<span className="fl color-grey3 font-16">二级板块管理</span>
<a onClick={this.createSubPlateEvent} className="fr middle-default-btn small-green-btn">新建板块</a>
</div>
{forumTagList()}
</div>
)
}
}
export default PrePlateManage;

View File

@ -0,0 +1,146 @@
import React, { Component } from 'react';
import { Spin , Pagination } from 'antd';
import '../exchange.css';
import './manage.css';
import Empty from '../Empty'
import ItemLeft from './ItemLeft';
import PassItem from './PassItem';
import axios from 'axios'
class SubCheckItem extends Component {
constructor(props){
super(props);
this.state={
page:1,
data:undefined,
isSpin:true,
limit:10
}
}
componentDidMount=()=>{
this.getList(1);
}
getList = (page) =>{
this.setState({
isSpin:true
})
const { plateId } = this.props.match.params;
const url = `/forum_sections/${plateId}/unchecked_memos.json`;
axios.get(url,{params:{
page
}}).then((result)=>{
if(result){
this.setState({
data:result.data,
isSpin:false
})
}
}).catch((error)=>{
})
}
// 禁言
stopEvent=(id,banned,user_id)=>{
const { current_user } = this.props;
const url =`/memos/${id}/banned_user.json`
axios.post((url),{
user_id:user_id,
banned
}).then((result)=>{
if(result){
const { page } = this.state;
this.props.showNotification(result.data.message);
this.getList(page);
}
}).catch((error)=>{
})
console.log("banned",banned);
}
// 分页
changePageEvent=(page)=>{
this.setState({
page
})
this.getList(page);
}
render(){
const { data , page , isSpin , limit } = this.state;
const { current_user } = this.props;
const pageCom = (
data && data.memos_count > limit &&
<div className="edu-txt-center pt30 pb30">
<Pagination showQuickJumper current={page} total={data.memos_count} pageSize={limit} onChange={this.changePageEvent}/>
</div>
)
const dataList = (
data && data.memos_lists && data.memos_lists.length > 0 ?
<div className="pl30 pr30">
{
data.memos_lists.map((item,key)=>{
const userInfo = {
forum_id:item.forum_id,
memo_id:item.memo_id,
user_url:item.user_url,
username:item.username,
time:item.time,
image_url:item.image_url,
memo_title:item.memo_title,
forum_title:item.forum_title,
user_id:item.user_id
}
const passItem = {
refresh:this.getList,
page,
id:item.memo_id
}
return(
<div className="moderatorItems">
<div className="df">
<ItemLeft {...userInfo}/>
<div className="flex-align-bottom between_">
{
current_user && current_user.admin &&
( item.is_banned ?
<p className="edu-txt-right color-red mb15 mt12 c_point" onClick={()=>this.stopEvent(item.memo_id,0,item.user_id)}><i className="iconfont icon-jinzhi font-16 mr10"></i></p>
:
<p className="edu-txt-right color-grey-9 mb15 mt12 c_point" onClick={()=>this.stopEvent(item.memo_id,1,item.user_id)}><i className="iconfont icon-jinzhi font-16 mr10"></i></p>
)
}
<PassItem {...this.props} {...this.state} {...passItem} ></PassItem>
</div>
</div>
</div>
)
})
}
{ pageCom }
</div>
:
<div className="edu-back-white pt50 pb50">
<Empty></Empty>
</div>
)
return(
<Spin spinning={isSpin}>
{ dataList }
</Spin>
)
}
}
export default SubCheckItem;

View File

@ -0,0 +1,148 @@
import React, { Component } from 'react';
import { Spin , Pagination } from 'antd';
import '../exchange.css';
import './manage.css';
import Empty from '../Empty'
import ItemLeft from './ItemLeft';
import PassItem from './PassItem';
import axios from 'axios'
class SubCheckReplyItem extends Component {
constructor(props){
super(props);
this.state={
page:1,
data:undefined,
isSpin:true,
limit:10
}
}
componentDidMount=()=>{
this.getList(1);
}
getList = (page) =>{
this.setState({
isSpin:true
})
const { plateId } = this.props.match.params;
const url = `/forum_sections/${plateId}/unchecked_replies.json`;
axios.get(url,{params:{
page
}}).then((result)=>{
if(result){
this.setState({
data:result.data,
isSpin:false
})
}
}).catch((error)=>{
})
}
// 禁言
stopEvent=(id,banned,user_id)=>{
const { current_user } = this.props;
const url =`/memos/${id}/banned_user.json`
axios.post((url),{
user_id:user_id,
banned
}).then((result)=>{
if(result){
const { page } = this.state;
this.props.showNotification(result.data.message);
this.getList(page);
}
}).catch((error)=>{
})
}
// 分页
changePageEvent=(page)=>{
this.setState({
page
})
this.getList(page);
}
render(){
const { data , page , isSpin , limit } = this.state;
const { current_user } = this.props;
const pageCom = (
data && data.memos_count > limit &&
<div className="edu-txt-center pt30 pb30">
<Pagination showQuickJumper current={page} total={data.memos_count} pageSize={limit} onChange={this.changePageEvent}/>
</div>
)
const dataList = (
data && data.replies_lists && data.replies_lists.length > 0 ?
<div className="pl30 pr30">
{
data.replies_lists.map((item,key)=>{
const userInfo = {
source_id:item.source_id,
user_url:item.user_url,
username:item.username,
time:item.time,
image_url:item.image_url,
reply_content:item.reply_content,
source_title:item.source_title,
user_id:item.user_id,
id:item.reply_id
}
const passItem = {
refresh:this.getList,
page,
id:item.reply_id
}
return(
<div className="moderatorItems">
<div className="df">
<ItemLeft {...userInfo}/>
<div className="flex-align-bottom between_">
{
current_user && current_user.admin &&
( item.is_banned ?
<p className="edu-txt-right color-red mb15 mt12 c_point" onClick={()=>this.stopEvent(item.reply_id,0,item.user_id)}><i className="iconfont icon-jinzhi font-16 mr10"></i></p>
:
<p className="edu-txt-right color-grey-9 mb15 mt12 c_point" onClick={()=>this.stopEvent(item.reply_id,1,item.user_id)}><i className="iconfont icon-jinzhi font-16 mr10"></i></p>
)
}
<PassItem {...this.props} {...this.state} {...passItem} ></PassItem>
</div>
</div>
</div>
)
})
}
{ pageCom }
</div>
:
<div className="edu-back-white pt50 pb50">
<Empty></Empty>
</div>
)
return(
<Spin spinning={isSpin}>
{ dataList }
</Spin>
)
}
}
export default SubCheckReplyItem;

View File

@ -0,0 +1,174 @@
import React, { Component } from 'react';
import { Spin , Pagination } from 'antd';
import Tags from '../TagComponent/Index';
import '../exchange.css';
import './manage.css';
import Empty from '../Empty'
import ItemLeft from './ItemLeft';
import PassItem from './PassItem';
import axios from 'axios'
class SubSendItem extends Component {
constructor(props){
super(props);
this.state={
page:1,
data:undefined,
isSpin:true,
limit:10
}
}
componentDidMount=()=>{
this.getList(1);
}
getList = (page) =>{
this.setState({
isSpin:true
})
const { plateId } = this.props.match.params;
const url = `/forum_sections/${plateId}/checked_memos.json`;
axios.get(url,{params:{
page
}}).then((result)=>{
if(result){
this.setState({
data:result.data,
isSpin:false
})
}
}).catch((error)=>{
})
}
// 删除-已发布的帖子
deleteEvent=(id)=>{
const { page } = this.state;
const url = `/memos/${id}/destroy.json`;
axios.post(url).then(result=>{
if(result){
this.props.showNotification(result.data.message);
this.getList(page);
}
}).catch(error=>{
})
}
// 取消推荐/推荐-已发布的帖子
isFineEvent=(id,flag)=>{
const { page } = this.state;
const url = `/memos/${id}/is_fine.json`;
axios.post(url,{
is_fine:flag ? 0 : 1
}).then(result=>{
if(result){
this.props.showNotification(result.data.message);
this.getList(page);
}
}).catch(error=>{
})
}
// 取消置顶/置顶-已发布的帖子
stickyEvent=(id,flag)=>{
const { page } = this.state;
const url = `/memos/${id}/set_top_or_down.json`;
axios.get(url,{params:{
sticky:flag ? 0 : 1
}}).then(result=>{
if(result){
this.props.showNotification(result.data.message);
this.getList(page);
}
}).catch(error=>{
})
}
// 禁言
stopEvent=(id,banned)=>{
const { current_user } = this.props;
const url =`/memos/${id}/banned_user.json`
axios.post((url),{
user_id:current_user && current_user.user_id,
banned
}).then((result)=>{
if(result){
const { page } = this.state;
this.props.showNotification(result.data.message);
this.getList(page);
}
}).catch((error)=>{
})
}
// 分页
changePageEvent=(page)=>{
this.setState({
page
})
this.getList(page);
}
render(){
const { data , page , isSpin , limit } = this.state;
const pageCom = (
data && data.memos_count > limit &&
<div className="edu-txt-center pt30 pb30">
<Pagination showQuickJumper current={page} total={data.memos_count} pageSize={limit} onChange={this.changePageEvent}/>
</div>
)
const dataList = (
data && data.memos_lists && data.memos_lists.length > 0 ?
<div className="pl30 pr30">
{
data.memos_lists.map((item,key)=>{
const userInfo = {
forum_id:item.forum_id,
memo_id:item.memo_id,
user_url:item.user_url,
username:item.username,
time:item.time,
image_url:item.image_url,
memo_title:item.memo_title,
forum_title:item.forum_title
}
return(
<div className="moderatorItems">
<div className="df">
<ItemLeft {...userInfo}/>
<div>
<p className="edu-txt-right color-red mb15 mt12" style={{height:"33px"}}>
<Tags bestClass={item.is_fine ? "mb5 ml15" : undefined} topClass={item.sticky ? "mb10 ml15" : undefined}></Tags>
</p>
<p className="ml50 color-grey-9">
<span className="c_point" onClick={()=>this.stickyEvent(item.memo_id,item.sticky)}>{item.sticky?"取消置顶":"置顶"}</span>
<span className="sendPoint c_point font-14" onClick={()=>this.isFineEvent(item.memo_id,item.is_fine)}>{item.is_fine?"取消推荐":"推荐"}</span>
<span className="sendPoint c_point font-14" onClick={()=>this.deleteEvent(item.memo_id)}>删除</span>
</p>
</div>
</div>
</div>
)
})
}
{ pageCom }
</div>
:
<div className="edu-back-white pt50 pb50">
<Empty></Empty>
</div>
)
return(
<Spin spinning={isSpin}>
{ dataList }
</Spin>
)
}
}
export default SubSendItem;

View File

@ -0,0 +1,134 @@
.moderatorMenu .ant-tabs-ink-bar{
display: none!important;
}
.moderatorItems{
padding:20px 0px;
border-bottom: 1px solid #eaeaea;
}
.moderatorItems:last-child{
border-bottom: none;
}
.ItemsHeadPhoto .custom-img{
width:36px;
height: 36px;
margin-right: 15px;
}
.reply_manage_content img{
width: 30px
}
/* 一级板块管理-版主申请 */
.applyList{
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between
}
.applyList > div{
background: #fff;
width: 585px;
display: flex;
padding:20px 30px;
box-sizing: border-box;
margin-bottom: 20px;
}
/* 表单form---标题和内容在统一行 */
.formInline .ant-row.ant-form-item{
display: flex
}
.formInline .ant-col.ant-form-item-control-wrapper{
flex: 1;
}
/* 二级板块管理 */
.subPlateList{
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content:space-between;
}
.subPlateList > div{
width:585px;
height: 180px;
background: #fff;
position: relative;
margin-bottom: 20px;
}
.subPlateList > div .subPlateItem{
position: absolute;
width: 100%;
left: 0px;
top:0px;
padding-left:30px;
box-sizing: border-box;
background: #fff;
}
.subPlateList > div .subPlateItem.active{
box-shadow: 0px 5px 26px rgba(0,0,0,0.1);
z-index: 1;
}
.subPlateItem_head{
display: flex;
justify-content: space-between;
height: 50px;
line-height: 50px;
border-bottom: 1px solid #f4f4f4;
margin-right: 30px;
}
.subPlateItem.active .plateManager > ul{
width: auto;
height: auto;
display: inline
}
.plateManager > ul{
display: inline-block;
width: 345px;
height: 60px;
overflow: hidden;
}
.font-36{font-size: 36px!important;}
.plateManager > ul > li{
display: flex;
flex-direction: column;
font-size: 12px;
align-items: center;
float: left;
margin-right: 30px;
text-align: center;
margin-bottom: 20px;
position: relative;
}
.deletePlateIcon{
position: absolute;
top: -11px;
right: -8px;
font-size: 18px;
color: #FF5555;
cursor: pointer;
}
.completeIcon{
background: #5091FF;
width: 36px;
height: 36px;
line-height: 36px;
font-size: 12px;
color: #fff;
text-align: center;
float: right;
border-radius: 50%;
margin-top: 18px;
}
/* 添加版主弹框 */
.addPlateModal .ant-modal-body{
padding:30px 0px!important;
}
/* .addPlateModal .ant-table-tbody > tr > td{
padding:10px 5px;
} */
/*禁言两端对齐*/
.between_{
flex-direction: column;
justify-content: space-between;
}

View File

@ -0,0 +1,18 @@
.plate-left-Menu .ant-tabs-tab{
height: 80px;
line-height: 80px;
font-size: 16px;
margin-left: 30px;
margin-right: 0px;
padding:0px;
}
.plate-left-Menu .ant-tabs-bar{
margin-bottom: 0px;
border-bottom: 1px solid #f4f4f4;
background: #fff;
}
.plate-left-Menu .ant-tabs-extra-content{
margin-top: 18px;
margin-right: 30px;
}

View File

@ -0,0 +1,51 @@
import React, { Component } from 'react';
import { Tabs } from 'antd';
import './Menu.css';
const { TabPane } = Tabs;
class MenuCom extends Component{
constructor(props){
super(props);
this.state={
activeKey: ''
}
}
componentDidMount () {
const { defaultUrlKey } = this.props;
this.setState({
activeKey: defaultUrlKey
})
}
changeTabs=(activeKey)=>{
this.setState({
activeKey:activeKey
})
const { changeTab } = this.props;
changeTab(activeKey);
}
render(){
const { menu_nav , btn , ...props } = this.props;
const tabs = menu_nav.map((tab)=>{
const Content = tab.content;
return(
<TabPane tab={tab.name} key={tab.key}>
<Content condition={tab.key} {...this.props} {...this.state}/>
</TabPane>
)
})
let { activeKey } = this.state;
return(
<Tabs { ...props } onChange={ this.changeTabs } activeKey={ activeKey } animated={false} tabBarExtraContent={ btn }>
{ tabs }
</Tabs>
)
}
}
export default MenuCom;

View File

@ -0,0 +1,115 @@
import React, { Component } from 'react';
import ExchangeRight from '../ExchangeRight'
import '../exchange.css';
import './myExchange.css';
import Nav from '../NavComponent/Index'
import MyTopic from './MyTopic';
import MyInteresting from './MyInteresting';
import MyEnshrine from './MyEnshrine';
import MenuWraps from '../MenuComponent/Menu'
import axios from 'axios';
const menu_nav = [
{
name:"我的话题",
key:`MyTopic`,
content:MyTopic
},
{
name:"我的收藏",
key:`MyEnshrine`,
content:MyEnshrine
},
{
name:"我感兴趣的论坛",
key:`MyInteresting`,
content:MyInteresting
}
]
class Index extends Component {
constructor(props){
super(props);
this.state={
activeKey:"MyTopic",
data:undefined
}
}
componentDidMount=()=>{
this.getMain();
}
getMain=()=>{
const url =`/my_memos/recommend_memos`;
axios.get(url).then(result=>{
if(result){
this.setState({
data:result.data
})
}
}).catch(error=>{
})
}
// 切换第一个nav
changeTab=(activeKey)=>{
// console.log('1--',activeKey);
this.setState({
activeKey
})
}
render(){
let pathname = this.props.location.pathname;
let urllength = pathname.split("/").length;
let urlLast = pathname.split("/")[urllength-1];
const { current_user } = this.props;
const { activeKey , data } = this.state;
const NavMap=[
{
url:current_user && current_user.user_url,
name:current_user && current_user.username
},
{
url:"/forums",
name:"论坛交流"
},
{
name:activeKey === "MyTopic" ? "我的话题" :activeKey === "MyEnshrine" ? "我的收藏" : "我感兴趣的论坛"
}
]
return(
<div className="educontent">
<Nav className="mt30" NavMap = {NavMap}></Nav>
<div className="clearfix F_panel" style={{marginTop:"10px"}}>
<div className="fl with76 pr20">
<div>
<MenuWraps
className="plate-left-Menu mainNav-style"
{...this.props}
{...this.state}
menu_nav={menu_nav}
defaultUrlKey={urlLast}
changeTab={this.changeTab}/>
</div>
</div>
<ExchangeRight
hideSearchPanel={true}
hottest_memos={data && data.hottest_memos}
recommend_memos={data && data.recommend_memos}
></ExchangeRight>
</div>
</div>
)
}
}
export default Index;

View File

@ -0,0 +1,17 @@
import React, { Component } from 'react';
import MyTopicItem from './MyTopicItem';
class MyEnshrine extends Component {
render(){
return(
<div>
<MyTopicItem {...this.props} {...this.state} urlName={'my_memos/my_watched'}></MyTopicItem>
</div>
)
}
}
export default MyEnshrine;

View File

@ -0,0 +1,101 @@
import React, { Component } from 'react';
import { Spin } from 'antd';
import { Link } from 'react-router-dom'
import Empty from '../Empty';
import './myExchange.css';
import box1 from '../images/box1.png';
import box2 from '../images/box2.png';
import box3 from '../images/box3.png';
import box4 from '../images/box4.png';
import box5 from '../images/box5.png';
import box6 from '../images/box6.png';
import axios from 'axios';
const backImgArray = [box1,box2,box3,box4,box5,box6];
class MyInteresting extends Component {
constructor(props){
super(props);
this.state={
forum_details:undefined,
isSpin:true
}
}
componentDidMount = () =>{
this.getInfo();
}
getInfo = () =>{
this.setState({
isSpin:true
})
const url = `/my_memos/my_interested`;
axios.get(url).then((result)=>{
if(result){
this.setState({
forum_details:result.data.forum_details,
isSpin:false
})
}
}).catch(error=>{
})
}
// 取消收藏
cancelEnshrineEvent=(id)=>{
const url = `/memos/forum_memos/${id}/is_watch.json`;
axios.post((url),{
is_watch:0
}).then(result=>{
if(result){
this.getInfo();
}
}).catch(error=>{
})
}
render(){
const { forum_details , isSpin } = this.state;
const divList = ( forum_details && forum_details.length > 0 ?
<div className="ForumList">
{
forum_details.map((item,key) => {
const index = Number(Number((key)%6)+1)-1;
// console.log(index);
return(
<div className="interestItem">
<div className="interestingUpper" style={{backgroundImage:`url(${backImgArray[index]})`}}>
<div>
<p className="font-20 color-white mb10">{item.title}</p>
<p className="color-white font-12 edu-txt-center">{item.memos_count} 个话题</p>
</div>
</div>
<span className="interestingOperate">
<span className="operateBtn c_point" onClick={()=>this.cancelEnshrineEvent(`${item.id}`)}>取消收藏</span>
<Link className="operateBtn color-blue" to={`/forums/theme/${item.id}`}>查看</Link>
</span>
</div>
)
})
}
</div>
:
<div className="pt50 pb50 edu-back-white"><Empty/></div>
)
return(
<Spin spinning={isSpin}>
<div style={{backgroundColor:"#fafafa"}}>
{ divList }
</div>
</Spin>
)
}
}
export default MyInteresting;

View File

@ -0,0 +1,59 @@
import React, { Component } from 'react';
import MenuWraps from '../MenuComponent/Menu';
import MyTopicItem from './MyTopicItem';
import './myExchange.css';
const menu_nav = [
{
name:"发表的话题",
key:`published`,
content:MyTopicItem
},
{
name:"回复的话题",
key:`replied`,
content:MyTopicItem
},
{
name:"看过的话题",
key:`watched`,
content:MyTopicItem
}
]
class MyTopic extends Component {
constructor(props){
super(props);
this.state={
activeKey:"published"
}
}
// 切换第2个nav发表的话题、回复的话题、看过的话题
changeTab=(activeKey)=>{
this.setState({
activeKey
})
}
render(){
let { activeKey } = this.state;
return(
<div>
<MenuWraps
{...this.props}
{...this.state}
className="plate-left-Menu"
subActiveKey={activeKey}
menu_nav={menu_nav}
defaultUrlKey={activeKey}
changeTab={this.changeTab}
urlName={'my_memos'}
/>
</div>
)
}
}
export default MyTopic;

View File

@ -0,0 +1,239 @@
import React, { Component } from 'react';
import { Dropdown , Menu , Icon , DatePicker , Pagination , Spin } from 'antd';
import { Link } from 'react-router-dom'
import moment from 'moment'
import Empty from '../Empty'
import ExchangeItems from '../ExchangeItem';
import './myExchange.css'
import '../exchange.css'
import axios from 'axios';
const { SubMenu } = Menu;
const dateFormat = 'YYYY-MM-DD';
class MyTopicItem extends Component {
constructor(props){
super(props);
this.state={
// 筛选条件
memo_type:undefined,
forum_section_id:undefined,
is_hidden:undefined,
is_hidden_name:undefined,
start_time:undefined,
end_time:undefined,
page:1,
plateName:undefined,
// 分页
pageSize:15,
// 数据
data:undefined,
isSpin:true
}
}
// 搜索
searchEvent=()=>{}
// 清除
clearEvent=()=>{}
componentDidMount=()=>{
this.refresh();
}
// 我的话题和我的收藏公用当前组件但接口不同根据url判断调用哪个接口
getMytopicInfo = (forum_section_id,is_hidden,start_time,end_time,page) =>{
this.setState({
isSpin:true
})
const { subActiveKey , urlName } = this.props;
const url = `/${urlName}`;
axios.get(url,{params:{
memo_type:subActiveKey,
forum_section_id,
is_hidden,
start_time,
end_time,
page
}}).then(result=>{
if(result){
this.setState({
data:result.data,
isSpin:false
})
}
}).catch(error=>{
})
}
// 刷新时需要用到
refresh=()=>{
const {forum_section_id,is_hidden,start_time,end_time } = this.state;
this.getMytopicInfo(forum_section_id,is_hidden,start_time,end_time,1);
}
// 切换分页
changePageEvent = (page) =>{
this.setState({
page
})
const {forum_section_id,is_hidden,start_time,end_time } = this.state;
this.getMytopicInfo(forum_section_id,is_hidden,start_time,end_time,page);
}
// 选择板块
changeForumId=(plateId,name)=>{
this.setState({
forum_section_id:plateId,
plateName:name
})
const {is_hidden,start_time,end_time , page } = this.state;
this.getMytopicInfo(plateId,is_hidden,start_time,end_time,page);
}
// 选择帖子状态
forumType=(type)=>{
this.setState({
is_hidden:type,
is_hidden_name:type === "show" ? "已发布的话题":"待审查的话题"
})
const {forum_section_id,start_time,end_time , page } = this.state;
this.getMytopicInfo(forum_section_id,type,start_time,end_time,page);
}
// 清除搜索条件
clearEvent=()=>{
this.setState({
forum_section_id:undefined,
plateName:undefined,
is_hidden:undefined,
is_hidden_name:undefined,
start_time:undefined,
end_time:undefined
})
this.getMytopicInfo(undefined,undefined,undefined,undefined,1);
}
// 选择开始时间
changeBeginEvent=(e,dateString)=>{
this.setState({
start_time:dateString
})
}
// 选择结束时间
changeEndEvent=(e,dateString)=>{
this.setState({
end_time:dateString
})
}
// 搜索
searchEvent=()=>{
this.setState({
page:1
})
const {forum_section_id,is_hidden,start_time,end_time } = this.state;
this.getMytopicInfo(forum_section_id , is_hidden , start_time , end_time , 1);
}
render(){
const { data , pageSize , page , plateName , is_hidden_name , start_time , end_time , isSpin } = this.state;
// 选择板块
const menuList =(
<div className="platePanel">
{
data && data.forum_sections && data.forum_sections.map(item => {
return(
<div className="plateItem">
<span className="plateItem_h"><a onClick={()=>this.changeForumId(item.id,item.name)}>{item.name}</a></span>
<ul className="plateUl">
{
item.children_tags && item.children_tags.map(i=>{
return(<li><a onClick={()=>this.changeForumId(i.id,i.title)}>{i.title}</a></li>)
})
}
</ul>
</div>
)
})
}
</div>
)
// 全部帖子
const forumList = (
<Menu>
<Menu.Item onClick={()=>this.forumType('hidden')}>待审查的话题</Menu.Item>
<Menu.Item onClick={()=>this.forumType('show')}>已发布的话题</Menu.Item>
</Menu>
)
const pagination = (
data && data.memos && data.memos_count > pageSize &&
<div className="edu-txt-center pt30 pb50">
<Pagination current={page} pageSize={pageSize} total={data.memos_count} showQuickJumper onChange={this.changePageEvent}></Pagination>
</div>
)
const dataList = (
<Spin spinning={isSpin}>
<div className="MyTopicSearch">
<Dropdown overlay={menuList}>
<a className="ant-dropdown-link">
{plateName || "选择板块"} <Icon type="down" />
</a>
</Dropdown>
<Dropdown overlay={forumList}>
<a className="ant-dropdown-link">
{ is_hidden_name || '全部帖子' } <Icon type="down" />
</a>
</Dropdown>
<div>
<span className="mr10">开始日期</span>
<DatePicker style={{width:"145px"}}
placeholder={'请选择开始时间'}
format={dateFormat}
value={start_time && moment(start_time,dateFormat)}
onChange={this.changeBeginEvent}
/>
<span className="ml15 mr10">结束日期</span>
<DatePicker style={{width:"145px"}}
placeholder={'请选择结束时间'}
format={dateFormat}
value={end_time && moment(end_time,dateFormat)}
onChange={this.changeEndEvent}
/>
</div>
<span className="df">
<a onClick={this.searchEvent} className="small-default-btn small-blue-btn mr15">搜索</a>
<a onClick={this.clearEvent} className="small-default-btn">清除</a>
</span>
<span className="color-grey9 font-12"><span className="color-blue">{ data && data.memos_count }</span></span>
</div>
{
data && data.memos && data.memos.length>0 ?
<div>
<ExchangeItems memos = { data && data.memos } {...this.props} {...this.state} refresh={this.refresh}></ExchangeItems>
{ pagination }
</div>
:
<div className="pt50 pb50 edu-back-white">
<Empty/>
</div>
}
</Spin>
)
return( dataList )
}
}
export default MyTopicItem;

View File

@ -0,0 +1,56 @@
.MyTopicSearch{
display: flex;
border-bottom: 1px solid #f4f4f4;
padding:20px 30px;
align-items: center;
justify-content: space-between;
background: #fff;
}
/* 我感兴趣的论坛 */
.ForumList{
display: flex;
flex-flow: row;
flex-wrap:wrap;
justify-content: space-between;
}
.interestItem{
width: 280px;
margin:20px 0px;
background: #FFFFFF;
}
.interestItem .interestingUpper{
height: 150px;
background-repeat:no-repeat;
background-size:100% 100%;
background-color:#fff ;
display: flex;
flex-flow: row;
align-items: center;
justify-content: center;
}
.interestingOperate{
display: flex;
height: 50px;
line-height: 50px;
}
.interestingOperate .operateBtn{
width: 50%;
display: block;
text-align: center;
position: relative;
color: #666666;
}
.interestingOperate .operateBtn:first-child::after{
height: 24px;
width:1px;
top:13px;
position: absolute;
right: 0px;
content: '';
background: #eee;
}
.mainNav-style > .ant-tabs-bar .ant-tabs-ink-bar{
display: none!important;
}

View File

@ -0,0 +1,28 @@
import React , { PureComponent } from "react";
import { Link } from 'react-router-dom';
import './nav.css'
class Index extends PureComponent{
render(){
const { NavMap , ...props } = this.props;
let navRouter = NavMap && NavMap.map((item) => {
return(
<React.Fragment>
{
item.name ? (item.url ?
<Link to={item.url} className="color-grey-9 nav_Link" >{ item.name }</Link>
:
<span className="color-grey3">{ item.name }</span>)
:""
}
</React.Fragment>
)
})
return(
<p {...props}>{ navRouter }</p>
)
}
}
export default Index;

View File

@ -0,0 +1,13 @@
.nav_Link{
position: relative;
margin-right: 20px;
}
.nav_Link::after{
position: absolute;
right: -15px;
content: '>';
top:0px;
color: #999;
height: 20px;
line-height: 18px;
}

36
src/exchange/Plate/All.js Normal file
View File

@ -0,0 +1,36 @@
import React, { PureComponent } from 'react';
import ExchangeItem from '../ExchangeItem'
import Empty from '../Empty'
import '../exchange.css'
import './plate.css'
class All extends PureComponent{
state = {
condition:"all"
}
componentDidMount = () =>{
const { condition } = this.props;
this.setState({
condition
})
}
render(){
const { memos } = this.props;
const memosList = (
memos && memos.length > 0 ?
<ExchangeItem memos={ memos } {...this.props} {...this.state}></ExchangeItem>
:
<div className="pt50 pb50">
<Empty/>
</div>
)
return(
memosList
)
}
}
export default All;

263
src/exchange/Plate/Index.js Normal file
View File

@ -0,0 +1,263 @@
import React, { Component } from 'react';
import { Pagination , Spin } from 'antd'
import {Link} from 'react-router-dom'
import '../exchange.css';
import './plate.css'
import Nav from '../NavComponent/Index';
import MenuWraps from '../MenuComponent/Menu';
import PlateRight from './PlateRight'
import axios from 'axios';
import All from './All'
const menu_nav = [
{
name:"全部",
key:`all`,
content:All
},
{
name:"推荐精华",
key:`is_fine`,
content:All
},
{
name:"我的话题",
key:`my_memos`,
content:All
},
{
name:"我参与的话题",
key:`my_topics`,
content:All
}
]
class Index extends Component {
constructor(props){
super(props);
this.state = {
collectFlag:false,
// 列表搜索相关
page:1,
search:undefined,
pageSize:10,
select_type:"all",
memos:undefined,
memos_count:0,
// 接口返回的所有数据
data:undefined,
forum_tag :undefined,
forum_moders:undefined,
forum_sections:undefined,
bread_crumb:undefined,
current_user:undefined,
isSpin:true
}
}
componentDidMount () {
this.InitData();
}
InitData=()=>{
this.setState({
isSpin:true
})
const { plateid } = this.props.match.params;
const { page , search , select_type} = this.state;
this.getInfos(plateid , page , search , select_type);
}
getInfos = (plateid , page , search , select_type) =>{
const url= `/memos/forum_memos/${plateid}.json`;
axios.get(url,{params:{
page,
search,
select_type
}}).then(result=>{
if(result){
this.setState({
data:result.data,
memos:result.data.memos,
memos_count:result.data.memos_count,
collectFlag:result.data.watched,
forum_tag :result.data.bread_crumb && result.data.bread_crumb.forum_tag,
forum_moders:result.data.forum_moders,
forum_sections:result.data.forum_sections,
bread_crumb:result.data.bread_crumb,
current_user:result.data.current_user,
isSpin:false
})
}
}).catch(error=>{
})
}
// 点击收藏
colectPlate = () =>{
const { collectFlag } = this.state;
const { plateid } = this.props.match.params;
const url = `/memos/forum_memos/${plateid}/is_watch.json`;
// is_watch:1为添加关注
axios.post(url,{
is_watch: collectFlag ? 0 : 1
}).then((result)=>{
if(result){
if(result.data.status === 0){
this.setState({
collectFlag:!collectFlag
})
this.props.showNotification(result.data.message);
}
}
}).catch((error)=>{
})
}
// 搜索
searchEvent=(e)=>{
this.setState({
search:e
})
const { plateid } = this.props.match.params;
const { select_type } = this.state;
this.getInfos(plateid , 1 , e , select_type);
}
// 切换菜单项
changeTabEvent = (key) =>{
this.setState({
select_type:key,
isSpin:true
})
const { plateid } = this.props.match.params;
const { search} = this.state;
this.getInfos(plateid , 1 , search , key);
}
// 切换分页
changePageEvent=(pageNum)=>{
this.setState({
page:pageNum
})
const { plateid } = this.props.match.params;
const { search , select_type} = this.state;
this.getInfos(plateid , pageNum , search , select_type);
}
render(){
let {
title,
collectFlag ,
memos ,
forum_tag ,
select_type,
forum_moders ,
forum_sections ,
page,
data,
pageSize,
bread_crumb ,
current_user,
isSpin } = this.state;
const { plateid } = this.props.match.params;
let pathname = this.props.location.pathname;
let urlLastLength = pathname.split("/").length;
let urlLast = pathname.split("/")[urlLastLength-1];
const children_bread_crumb = forum_tag && forum_tag.children_bread_crumb;
//获取当前版块标题
const title_post=forum_tag && ((children_bread_crumb && children_bread_crumb.title) || forum_tag.title);
// 顶部导航栏
const routerMap = [
{
name:current_user && current_user.username,
url:current_user && current_user.user_url
},
{
name:bread_crumb && bread_crumb.forum && bread_crumb.forum.title,
url:"/forums"
},
{
name:forum_tag && ((children_bread_crumb && children_bread_crumb.title) || forum_tag.title)
}
];
let collectMap = current_user? <span className="mt2 collecting" onClick={() => this.colectPlate()}>{collectFlag ?'取消收藏':'收藏'}</span>:<span className="mt2 collecting" ><a href={`/login`}> </a> </span>;
// 菜单行右侧按钮
//只有登录管理员可见
const btn = (current_user && current_user.admin_permission) ?<Link to={`/forums/manage/${plateid}`} className={"color-grey-6"}>版块管理</Link> : undefined;
console.log(btn);
// 操作列表item需要刷新页面以及切换菜单项需要重新调用参数
const commonEvent = {
refresh:this.InitData,
changeTab:this.changeTabEvent
}
// 列表分页
const pageination = (
data && data.memos_count > pageSize &&
<div className="mb50 edu-txt-center">
<Pagination showQuickJumper current={page} total={data && data.memos_count} pageSize={pageSize} onChange={this.changePageEvent}/>
</div>
)
return(
<div className="clearfix educontent pt20">
<Spin spinning={isSpin}>
<Nav NavMap = {routerMap} ></Nav>
<p className="mt30 mb10 clearfix">
<span className="font-22 color-grey3 mr20 fl lineh-25 task-hide" style = {{ maxWidth : "900px"}}>{title_post}</span>
{ collectMap }
</p>
<div className="clearfix">
<div className="fl with76 pr20">
<div className={"edu-back-white mb30"}>
<MenuWraps
{...this.props}
{...this.state}
{...commonEvent}
className="plate-left-Menu"
menu_nav={menu_nav}
btn={btn}
defaultUrlKey={urlLast}
{...this.state}/>
</div>
{ pageination }
</div>
<PlateRight
forum_sections={forum_sections}
forum_moders={forum_moders}
searchEvent={this.searchEvent}
{...this.props}
{...this.state}
></PlateRight>
</div>
</Spin>
</div>
)
}
}
export default Index;

View File

@ -0,0 +1,93 @@
import React, { PureComponent } from 'react';
import { getImageUrl } from 'educoder';
import { Link } from 'react-router-dom';
import '../exchange.css';
import './plate.css'
import ExchangeRightSearch from '../ExchangeRightSearch'
import Custom from '../CustomComponent/Index';
import axios from 'axios';
class PlateRight extends PureComponent {
// 申请版主
applyModerator = () =>{
this.props.confirm({
content: '是否确认申请版主?',
onOk: () => {
const { plateid } = this.props.match.params;
const url = `/forum_sections/${plateid}/user_apply`;
axios.post(url).then(result=>{
if(result){
this.props.showNotification(result.data.message);
}
}).catch(error=>{
})
},
onCancel() {
console.log('Cancel');
},
});
}
render(){
const { forum_sections , forum_moders , searchEvent , current_user }= this.props;
// admin_permission:是否是版主
const moderatorInfo = ( forum_moders && forum_moders.length > 0 &&
<div className="bc-white mb20">
<p className="clearfix r_part_title">
<img src={getImageUrl("images/plate/person.png")} width="18px" className="mr10 fl mt7" alt=""/>
<span className="color-grey3 font-16 fl">版主</span>
{
current_user && current_user.admin_permission === false && <a onClick={this.applyModerator} className="applyBtn fr mt3">申请版主</a>
}
</p>
<div className="moderatorPanel">
{ forum_moders.map((item,key)=>{
return(
<Custom className="moderatorInfo" hrefUrl={`/users/${item.user_login}`} img_url={getImageUrl(`images/${item.image_url}`)} name={item.username}></Custom>
)
}) }
</div>
</div>
)
const choiceInfo = ( forum_sections && forum_sections.length > 0 &&
<div className="bc-white mb20">
<p className="clearfix r_part_title">
<img src={getImageUrl("images/plate/plate.png")} width="18px" className="mr10 fl mt7" alt=""/>
<span className="color-grey3 font-16 fl">精选版块</span>
</p>
<ul className="choicePlate">
{
forum_sections.map((item,key) => {
return(
<li className="clearfix">
<span className="fl"><a href={`/forums/theme/${item.id}`}>{item.title}</a></span>
<span className="fr"><a href={`/forums/theme/${item.id}`} className="color-blue">{item.memo_size}</a></span>
</li>
)
})
}
</ul>
</div>
)
return(
<div className="fl with24">
<ExchangeRightSearch {...this.props} {...this.state} searchEvent={searchEvent}></ExchangeRightSearch>
{ moderatorInfo }
{ choiceInfo }
</div>
)
}
}
export default PlateRight;

View File

@ -0,0 +1,45 @@
/* plate */
.collecting{
height: 22px;
padding:0px 11px;
border: 1px solid #5091FF;
color: #5091FF;
border-radius: 4px;
line-height: 22px;
font-size: 12px;
float: left;
cursor: pointer;
}
.collected{
color: #999999;
border:1px solid #999999;
}
.applyBtn{
border:1px solid #5091FF;
color: #5091FF;
height: 26px;
line-height: 26px;
padding:0px 8px;
border-radius: 4px;
}
/* 版主 */
.moderatorPanel{
display: flex;
flex-wrap:wrap;
padding: 10px 15px 0px;
}
/* 精选版块 */
.choicePlate{
padding:10px 20px 0px 20px;
}
.choicePlate > li{
padding: 10px 0px;
}
.searchForHide .ant-tabs-bar.ant-tabs-top-bar{
display: none;
}

View File

@ -0,0 +1,155 @@
import React, { PureComponent } from 'react';
import './post.css'
import Item from '../Comments/CommentsItem'
import ItemImg from '../Comments/CommentsItemImg'
import axios from 'axios';
import CommentsSend from '../Comments/CommentsSend';
class CommentsIndex extends PureComponent{
// 调用父组件的刷新方法
refreshReply=()=>{
let { refresh , page} = this.props;
refresh(page);
}
// 删除评论
deleteReplyEvent=(id)=>{
const url = `/memos/${id}/destroy.json`
axios.post(url).then((result)=>{
if(result){
this.props.showNotification(result.data.message);
this.refreshReply();
}
}).catch((error)=>{
})
}
commentReplyEvent=(index,flag)=>{
console.log("1",flag);
this.props.showCommentEvent(index,true);
}
// 点赞评论
praiseEvent=(id)=>{
const url = `/discusses/${id}/plus.json`;
axios.post(url,{
container_type:'Memo',
type:1
}).then(result=>{
if(result){
this.refreshReply();
}
}).catch(error=>{
})
}
parseCommentContent = (oldContent) => {
if (oldContent && oldContent.startsWith('<') && oldContent.endsWith('>')) {
} else if (window.$('#md_div').length) { // 有这个临时处理md内容的dom
window.$('#md_div').html('')
// markdown to html
try {
var markdwonParser = window.editormd.markdownToHTML("md_div", {
markdown: oldContent,
emoji: true,
htmlDecode: "style,script,iframe", // you can filter tags decode
taskList: true,
tex: true, // 默认不解析
flowChart: true, // 默认不解析
sequenceDiagram: true // 默认不解析
});
oldContent = window.$('#md_div').html()
} catch (e) {
// TODO 可能公式parse时报错了
console.error(e)
}
}
return oldContent;
}
render(){
// 评论列表,当前用户信息
const { repliesData , current_user , page } = this.props;
const renderNum = (arrs,flag) =>{
return(<p>展开其余1条评论</p>)
}
//二级评论
const renderChild = (arrs) => {
return(
<div className="">
{
arrs.map((child)=>{
return(
<div className="commentsItem_infos">
<ItemImg image_url={child.image_url} ></ItemImg>
<div className="flex1">
<Item image_url={child.image_url} username={child.username} time={child.time} content={child.content} id={child.id}></Item>
</div>
</div>
)
})
}
</div>
)
}
const replyInfoWrap = (i,index)=> (
<React.Fragment>
<p className="edu-txt-right color-grey-6">
<span className={ i.user_praise ? "ml30":"ml30 c_point"} onClick={ i.user_praise ? undefined : ()=>this.praiseEvent(i.id)}>
<i className={ i.user_praise ? "iconfont icon-dianzan color-grey-9 font-16 mr5" : "iconfont icon-dianzan-xian color-grey3 font-16 mr5"}></i>
<span>{i.praise_count}</span>
</span>
{
current_user && (current_user.is_banned === false || current_user.admin) &&
<span className="ml30 c_point" onClick={()=>this.commentReplyEvent(index,i.commentsFlag)}>
<i className="iconfont icon-pinglun color-grey-9 font-16 mr5"></i><span>{i.replies_count}</span>
</span>
}
</p>
{
i.commentsFlag &&
<CommentsSend unique={`sub_${i.id}`} id={i.id} image_url={current_user && current_user.image_url} refresh={this.refreshReply}/>
}
</React.Fragment>
)
// 判断是否是管理员或者版主
const manage = current_user && (current_user.admin || current_user.banned_permission);
const itemMap = repliesData && repliesData.map((i,index)=>{
let _content = this.parseCommentContent(i.content);
return(
<div className="pre_stage" key={index}>
<div className="commentsItem_infos">
<ItemImg image_url={i.image_url} ></ItemImg>
<div className="flex1">
<Item username={i.username} time={i.time} content={_content} id={i.id} admin={manage} deleteReplyEvent={this.deleteReplyEvent}></Item>
{
i.children && i.children.length > 0 &&
<div className="sub_stage">
{renderChild(i.children)}
{/* { i.children.length > 1 && renderNum(i.children)} */}
</div>
}
{replyInfoWrap(i,index)}
</div>
</div>
</div>
)
})
return( itemMap )
}
}
export default CommentsIndex;

View File

@ -0,0 +1,128 @@
import React, { Component } from 'react';
import moment from 'moment'
import Select, {Option, OptGroup} from 'rc-select';
import 'rc-select/assets/index.css';
import { Upload, Icon, Modal, message } from 'antd';
import 'antd/lib/upload/style/index.css'
import 'antd/lib/modal/style/index.css'
import 'antd/lib/style/index.css'
import 'antd/lib/message/style/index.css'
import './MemoNew.css'
// -------------------------------------------
let uploadValidateFailed = false;
function beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isPNG = file.type === 'image/png';
const isGIF = file.type === 'image/gif';
if (!isJPG && !isPNG && !isGIF) {
message.error('只能上传图片文件!(jpg、png或gif)');
uploadValidateFailed = true;
return false;
}
const isLt2M = (file.size / 1024 / 1024) < 2;
if (!isLt2M) {
uploadValidateFailed = true;
message.error('图片大小必须小于 2MB!');
return false;
}
uploadValidateFailed = false;
return true;
}
class ImageUpload extends Component {
state = {
previewVisible: false,
previewImage: '',
fileList: [
// {
// uid: -1,
// name: 'xxx.png',
// status: 'done',
// url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
// }
],
};
handleCancel = () => {
this.setState({ previewVisible: false })
}
handlePreview = (file) => {
this.setState({
previewImage: file.url || file.thumbUrl,
previewVisible: true,
});
}
handleRemove = () => {
const { onImageUploadRemove } = this.props;
onImageUploadRemove()
}
componentWillReceiveProps(newProps, oldProps) {
// 初始化值
if (newProps.fileList && this.props.fileList.length != newProps.fileList.length) {
this.setState({
fileList: newProps.fileList
})
}
}
handleChange = ({ fileList }) => {
const { onImageUploadDone } = this.props;
if (fileList.length && fileList[0].status === 'done') {
onImageUploadDone(fileList[0])
}
if (!uploadValidateFailed) {
this.setState({ fileList })
}
}
render() {
const { previewVisible, previewImage, fileList } = this.state;
const { currentMemoId } = this.props;
const uploadButton = (
<div className="antuploadName">
<div className="antIconName"><Icon type="plus" /></div>
<div className="ant-upload-text">点击上传封面</div>
<div className="ant-upload-text">只支持JPGPNGJPEG大小不超过2M</div>
<div className="ant-upload-text">建议尺寸4:3</div>
</div>
);
return (
<div className="clearfix">
<Upload
action={"http://123.59.135.93/upload_memo_heads"}
listType="picture-card"
fileList={fileList}
onPreview={this.handlePreview}
onChange={this.handleChange}
onRemove={this.handleRemove}
beforeUpload={beforeUpload}
data={{
container_type: 'Memo',
container_id: currentMemoId ? currentMemoId : ''
}}
>
{fileList.length >= 1 ? null : uploadButton}
</Upload>
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</div>
);
}
}
export default ImageUpload;

248
src/exchange/Post/Index.js Normal file
View File

@ -0,0 +1,248 @@
import React, { Component } from 'react';
import { Pagination , Spin } from 'antd';
import '../exchange.css'
import './post.css'
import Nav from '../NavComponent/Index'
import Parse from './Parse';
import ExchangeItem from '../ExchangeItem'
import CommentsIndex from './CommentsIndex';
import CommentsSend from '../Comments/CommentsSend';
import RenderHtml from "../../components/render-html";
import FileList from '../../common/FileList';
import update from 'immutability-helper'
import axios from 'axios';
class Index extends Component{
constructor(props){
super(props);
this.state={
// 当前用户是否点赞
judge:true,
parseNum:0,
// 帖子用户信息
author_info:undefined,
// 当前用户信息
current_user:undefined,
// 面包屑
bread_crumb:undefined,
// 当前帖子详情
memos:undefined,
// 帖子评论
repliesData:undefined,
// 帖子评论分页相关
replyPage:1,
replyPageSize:10,
replyCount:0,
is_Spin:true
}
}
componentDidMount = () =>{
this.getInfo();
}
getInfo=()=>{
this.InitDetailInfo();
this.getReply(1);
}
InitDetailInfo = ()=>{
let postid = this.props.match.params.postid;
const url = `/memos/${postid}.json`;
axios.get(url).then((result)=>{
if(result.data && result.data.status === -1){
this.props.history.push("/forums");
}
if(result){
this.setState({
judge:result.data.memo.user_praise,
parseNum:result.data.memo && result.data.memo.praises_count,
author_info:result.data.author_info,
current_user:result.data.current_user,
bread_crumb:result.data.bread_crumb,
memos:result.data.memo,
is_Spin:false
})
}
}).catch((error)=>{
})
}
getReply = (replyPage) =>{
let postid = this.props.match.params.postid;
const url = `/memos/${postid}/more_reply.json`;
axios.get(url,{params:{
page:replyPage
}}).then((result)=>{
if(result){
this.setState({
repliesData:result.data.memo_replies,
replyCount:result.data.memos_count,
is_Spin:false
})
}
}).catch((error)=>{
})
}
// 点赞
thumbForum = () =>{
let postid = this.props.match.params.postid;
const url = `/discusses/${postid}/plus.json`
axios.post(url,{
container_type:'Memo',
type:1
}).then((result)=>{
if(result){
this.InitDetailInfo();
}
}).catch((error)=>{
})
}
// 切换评论分页
changeReplyEvent=(pageNumber)=>{
this.setState({
replyPage:pageNumber
})
this.getReply(pageNumber);
}
// 点击显示评论输入框
commentReplyEvent=(index,flag)=>{
console.log("2",flag);
this.setState(
(prevState) => ({
repliesData : update(prevState.repliesData, {[index]: { commentsFlag: {$set: flag} }}),
})
)
}
// 删除评论
deleteReplyEvent=(id)=>{
const url = `/memos/${id}/destroy.json`
axios.post(url).then((result)=>{
if(result){
this.props.showNotification(result.data.message);
let{ replyPage } = this.state;
this.getReply(replyPage);
}
}).catch((error)=>{
})
}
render(){
let {
judge , parseNum , author_info , current_user , bread_crumb , memos , repliesData ,
replyPage,
replyPageSize,
replyCount,
is_Spin
} = this.state;
let postid = this.props.match.params.postid;
const NavMap=[
{
url:`/users/${current_user && current_user.login}`,
name:current_user && current_user.username
},
{
url:"/forums",
name:bread_crumb && bread_crumb.forum && bread_crumb.forum.title
},
{
url:`/forums/theme/${bread_crumb && bread_crumb.forum_tag && bread_crumb.forum_tag.id}`,
name:bread_crumb && bread_crumb.forum_tag && bread_crumb.forum_tag.title
},
{
name:"帖子详情"
}
]
//插入字段从author_info表内选取
const memos_list = [{
...memos,
image_url:author_info && author_info.image_url,
username:author_info && author_info.username,
user_login:author_info && author_info.login
}];
// 评论列表
const memo_replies = repliesData;
const repliesList = (
memo_replies && memo_replies.length > 0 &&
<div className="commentsForm">
<p className="replyTitle">
<span className="font-24 color-grey-3 mr20">全部回复</span>
<span className="color-grey9 font-20">{replyCount}</span>
</p>
<CommentsIndex {...this.props} {...this.state} refresh={this.getReply} page={replyPage} showCommentEvent={this.commentReplyEvent}></CommentsIndex>
</div>
)
// 附件显示
const fileListCom = ( memos && memos.attachment_url && <FileList list = { memos.attachment_url } className="fileTeam"></FileList> )
return(
<Spin spinning={is_Spin}>
<div className="educontent">
<Nav className="mt20 mb20" NavMap = {NavMap}></Nav>
<div className="educontent-min bc-white mb30">
{/* 调用和首页共用的列表item传了enshine就代表底部右侧操作按钮只有收藏或者取消收藏否则就是置顶、推荐等 */}
<ExchangeItem
{...this.props}
{...this.state}
memos={memos_list}
current_user = { current_user }
detail={true}
refresh={this.getInfo}
></ExchangeItem>
<div className="postContent">
{/* 帖子详情 */}
<RenderHtml className="postDetail" value={memos && memos.content} />
{/* 文件列表 */}
{ fileListCom }
{/* 点赞 */}
<div className="edu-txt-center pb40">
<Parse judge={!judge} current_user = { current_user } num = {parseNum} clickEvent = { this.thumbForum }></Parse>
</div>
{/* 发送评论部分 */}
{
current_user && (current_user.is_banned === false || current_user.admin) &&
<CommentsSend unique={`main_Send`} id={postid} refresh={()=>this.getReply(replyPage)}/>
}
{/* 评论列表 */}
{ repliesList }
</div>
</div>
{
replyCount && replyCount > replyPageSize ?
<div className="edu-txt-center mb50">
<Pagination showQuickJumper current={replyPage} pageSize={replyPageSize} total={replyCount} onChange={this.changeReplyEvent}></Pagination>
</div>:""
}
</div>
</Spin>
)
}
}
export default Index;

View File

@ -0,0 +1,128 @@
.compilegoback{
float:right;
color:#676767 !important;
cursor: pointer;
}
.compilegodell{
float:right;
margin-right:20px;
color:#dadada;
cursor: pointer;
}
.Releasethetitle{
height: 48px !important;
padding-left: 20px !important;
background:#f4f4f4 !important;
}
.Releasethetitle:focus{
background:#FFF !important;
}
.Releasethetitle::-webkit-input-placeholder{
color:#999999;
}
.Releasethetitle:-moz-placeholder{
color:#999999;
}
.Releasethetitle::-moz-placeholder{
color:#999999;
}
.Releasethetitle:-ms-input-placeholder{
color:#999999;
}
.ecSelectbox{
margin-top: 30px;
}
.ant-select-selection__placeholder{
color:#999999;
}
.antuploadName{
width: 280px;
height: 170px;
}
.antuploadName div:nth-child(2){
color:#999898;
font-size:16px;
}
.antuploadName div:nth-child(3){
margin-top: 10px;
color:#d2d2d2;
}
.antuploadName div:nth-child(4){
margin-top: 10px;
color:#d2d2d2;
}
.antIconName{
width: 30px;
height: 30px;
background: #21B351;
border: 1px solid #21B351;
border-radius: 50%;
color: #FFF;
text-align: center;
line-height: 25px;
margin: 40px auto 12px;
font-size: 20px;
position: relative;
}
.antIconName i{
position: absolute;
top: 4px;
left: 3.8px;
}
.ReleasTopic{
color:#333;
font-size:18px;
}
.ant-calendar-picker-input::-webkit-input-placeholder{
color:#999999;
}
.ant-calendar-picker-input:-moz-placeholder{
color:#999999;
}
.ant-calendar-picker-input::-moz-placeholder{
color:#999999;
}
.ant-calendar-picker-input:-ms-input-placeholder{
color:#999999;
}
.ml57{
margin-left:57px;
}
.newdefalutSubmitbtn{
width: 120px !important;
height: 38px !important;
line-height: 38px !important;
margin-top: -5px;
}
.newdefalutCancelbtn{
width: 120px !important;
height: 38px !important;
line-height: 38px !important;
margin-top: -5px;
border: 1px solid #CDCDCD;
}
.ecSelect{
background: #f4f4f4 !important;
padding-left: 4px !important;
height: 48px;
overflow: hidden;
width: 526px;
position: relative;
}
.newLeftgrey{
margin-left: 18px;
}
.uploadBtnclick{
margin-left: 34px;
}
.ant-select-selection--multiple{
height:100%;
border: 1px solid transparent;
}

View File

@ -0,0 +1,811 @@
import React, { Component } from 'react';
import axios from 'axios';
import { Select } from 'antd';
import 'antd/lib/select/style/index.css'
import 'antd/lib/date-picker/style/index.css'
import zhCN from 'antd/lib/date-picker/locale/zh_CN';
import './MemoNew.css'
import './post.css'
import '../exchange.css'
import ImageUpload from './ImageUpload'
const Option = Select.Option;
const $ = window.$;
const isDevServer = window.location.port === "3007";
let origin = window.location.origin;
let path = "/editormd/lib/" // origin + '/react/build/js/editormd/lib/'
if (isDevServer) {
origin = 'http://localhost:3000'
path = 'http://localhost:3000/editormd/lib/'
}
// load
if (!window.postUpMsg) {
$.getScript(
`${origin}/javascripts/attachments.js`,
(data, textStatus, jqxhr) => {
});
}
// editorMD to create
/**
*
* @param id 渲染DOM的id
* @param width 宽度
* @param high 高度
* @param placeholder
* @param imageUrl 上传图片的url
* @returns {*} 返回一个editorMD实例
*/
function create_editorMD(id, width, high, placeholder, imageUrl, callback){
var editorName = window.editormd(id, {
width : width,
height : high,
syncScrolling : "single",
//你的lib目录的路径我这边用JSP做测试的
path : path , // "/editormd/lib/"
tex : true,
tocm : true,
emoji : true,
taskList : true,
codeFold : true,
searchReplace : true,
htmlDecode : "style,script,iframe",
sequenceDiagram : true,
autoFocus: false,
toolbarIcons : function() {
// Or return editormd.toolbarModes[name]; // full, simple, mini
// Using "||" set icons align right.
return ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "|", "testIcon", "testIcon1", '|', "image", "table", '|', "watch", "clear" ]
},
toolbarCustomIcons : {
testIcon : "<a type=\"inline\" class=\"latex\" ><div class='zbg'></div></a>",
testIcon1 : "<a type=\"latex\" class=\"latex\" ><div class='zbg_latex'></div></a>"
},
//这个配置在simple.html中并没有但是为了能够提交表单使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中方便post提交表单。
saveHTMLToTextarea : true,
// 用于增加自定义工具栏的功能可以直接插入HTML标签不使用默认的元素创建图标
dialogMaskOpacity : 0.6,
placeholder: placeholder,
imageUpload : true,
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL : imageUrl,//url
onload: function(){
// this.previewing();
$("#"+ id +" [type=\"latex\"]").bind("click", function(){
editorName.cm.replaceSelection("```latex");
editorName.cm.replaceSelection("\n");
editorName.cm.replaceSelection("\n");
editorName.cm.replaceSelection("```");
var __Cursor = editorName.cm.getDoc().getCursor();
editorName.cm.setCursor(__Cursor.line-1, 0);
});
$("#"+ id +" [type=\"inline\"]").bind("click", function(){
editorName.cm.replaceSelection("$$$$");
var __Cursor = editorName.cm.getDoc().getCursor();
editorName.cm.setCursor(__Cursor.line, __Cursor.ch-2);
editorName.cm.focus();
});
$("[type=\"inline\"]").attr("title", "行内公式");
$("[type=\"latex\"]").attr("title", "多行公式");
callback && callback()
}
});
return editorName;
}
class MemoNew extends Component {
constructor(props) {
super(props)
this.state = {
memoSubject: '',
memoContent: '',
memoType: undefined,
memoTime: undefined,
memoRepertoire: '',
memoLanguage: [],
fileList: [],
memoSubjectLength:undefined,
repertoires: [],
currentSelectRepertoiresIndex: -1,
repertoiresTagMap: {},
// 主题板块
smallOption:undefined,
bigPlateId:undefined,
smallPlateId:undefined,
}
}
onCommit() {
const { memoSubject , memoLanguage, currentMemoId, attachmentData , bigPlateId , smallPlateId} = this.state;
const { showSnackbar } = this.props;
this.setState({
memoSubjectLength : memoSubject.length
})
if (!memoSubject) {
showSnackbar('请先输入标题')
return
}
let mdVal;
try {
mdVal = this.taskpass_editormd.getValue()
} catch (e) {
showSnackbar('编辑器还未加载完毕,请稍后')
return
}
if (!mdVal) {
showSnackbar('请输入话题内容')
return
}
if (!bigPlateId) {
showSnackbar('请选择一级板块');
return
}
/*
<meta content="authenticity_token" name="csrf-param" />
<meta content="G7peAyb1T37RvdwxnVUKmTXuL8T7FaBze5mK0j6MCKs=" name="csrf-token" />
http://localhost:3000/attachments/download/185790/Git-2.17.1.2-32-bit.exe
https://www.educoder.net/attachments/205112.js?attachment_id=1
*/
// collect attachments
const $ = window.$;
const attachmentsMap = {};
let lastIndex = 0;
$('#attachments_fields .attachment').each(( index, item ) => {
const filename = $(item).find('.upload_filename').val();
// $($('#attachments_fields .attachment')[0]).find('input:nth-child(6)').val()
const token = $(item).find('input:nth-child(6)').val()
const attachment_id = $(item).find('input:nth-child(7)').val()
attachmentsMap[index] = {
filename,
token,
attachment_id
}
lastIndex = index;
})
if (attachmentData) {
const { response } = attachmentData
lastIndex++;
attachmentsMap[lastIndex] = {
filename: attachmentData.name,
token: '',
attachment_id: response.attachment_id
}
}
if (currentMemoId) {
this.updateMemo(attachmentsMap)
} else {
this.newMemo(attachmentsMap)
}
}
onCancel() {
const { currentMemoId } = this.state;
if (currentMemoId) { // 编辑
this.props.history.push(`/forums/${currentMemoId}`)
} else { // 新建
this.props.history.push(`/forums`)
}
}
onOkTime(value, dateString){
this.setState({
memoTime:dateString
})
}
updateMemo(attachmentsMap) {
const { memoSubject , currentMemoId, content, attachmentData , smallPlateId , bigPlateId} = this.state;
const mdVal = this.taskpass_editormd.getValue()
console.log('isContentEdit: ', mdVal === content);
const newMemoUrl = `/memos/${currentMemoId}/update`
const params = {
content_changed: this.contentChanged,
memo: {
subject: memoSubject ,
content: mdVal,
},
forum_id:bigPlateId,
attachments: attachmentsMap,
children_forum_id:smallPlateId
}
if (attachmentData) {
const attachment_id = attachmentData.response.attachment_id
if (attachment_id) {
params.attachment_id = attachment_id;
}
}
axios.post(newMemoUrl, params)
.then((response) => {
const { status, message } = response.data;
if (status === 0) {
window.$("html,body").animate({"scrollTop":0})
this.props.history.push(`/forums/${currentMemoId}`)
} else {
if (message.indexOf("Couldn't find Attachment with") !== -1) {
this.props.showSnackbar('附件不存在或正在被删除中,请稍后再试。。。')
} else {
this.props.showSnackbar(message)
}
}
}).catch((error) => {
console.log(error)
})
}
newMemo(attachmentsMap) {
const { memoSubject , attachmentData , smallPlateId , bigPlateId } = this.state;
const mdVal = this.taskpass_editormd.getValue()
const newMemoUrl = `/memos/create`
const params = {
memo: {
subject: memoSubject ,
content: mdVal
},
forum_id:bigPlateId,
attachments: attachmentsMap,
children_forum_id:smallPlateId,
}
if (attachmentData) {
const attachment_id = attachmentData.response.attachment_id
if (attachment_id) {
params.attachment_id = attachment_id;
// params.filename = attachmentData.name // 服务端处理 服务端使用默认的origin_filename
}
}
axios.post(newMemoUrl, params).then((response) => {
const { status, message, memo_id } = response.data;
if (status === 0) {
window.$("html,body").animate({"scrollTop":0})
this.props.history.push(`/forums/${memo_id}`)
} else {
if (message.indexOf("Couldn't find Attachment with") !== -1) {
this.props.showSnackbar('附件不存在或正在被删除中,请稍后再试。。。')
} else {
this.props.showNotification(message)
}
}
}).catch((error) => {
console.log(error)
})
}
onMemoDelete(memo) {
const deleteUrl = `/memos/${memo.id}`;
// 获取memo list
axios.delete(deleteUrl, {
withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.props.showSnackbar('删除成功');
this.props.history.push(`/forums`)
}
}).catch((error) => {
console.log(error)
})
}
componentWillUnmount() {
$('body>#root').off('onMemoDelete')
}
componentDidMount(){
$('body>#root').on('onMemoDelete', (event) => {
// const val = $('body>#root').data('onMemoDelete')
const val = window.onMemoDelete ;
this.onMemoDelete( JSON.parse(decodeURIComponent(val)) )
})
const newMemoUrl = `/memos/new`
axios.get(newMemoUrl,{
// withCredentials: true,
})
.then((response) => {
const data = response.data;
if ( data.current_user ) {
this.setState({
memo_tag: data.memo_type,
memoTime:""
})
const user = response.data.current_user;
user.tidding_count = response.data.tidding_count;
// this.props.initCommonState(user)
// 初始化 csrf meta
const $ = window.$
$('head').append( $('<meta content="authenticity_token" name="csrf-param" />') )
$('head').append( $(`<meta content="${response.data.csrf_token}" name="csrf-token" />`) )
}
}).catch((error) => {
console.log(error)
})
// 如果是编辑
const { match } = this.props
const memoId = match.params.memoId;
if (memoId) {
const memoUrl = `/memos/${match.params.memoId}/edit`;
axios.get(memoUrl).then((response) => {
const current_user = response.data.current_user;
if (current_user) {
const { content, forum_section, id, subject , attachments_url, memo_image_info,
memo_type, published_at , children_forum_section} = response.data;
let date;
let date_value;
this.initMD(content, memoId);
if(published_at){
date = new Date(published_at);
date_value=date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
}
const newState = {
currentMemoId: id,
attachments_url,
memoSubject: subject,
memo_tag: memo_type,
memoLanguage: forum_section.forum_id,
memoTime:date_value,
content,
bigPlateId: forum_section.forum_id,
smallPlateId: children_forum_section.children_forum_id
}
// 编辑,初始化小板块的下拉选项(如果大板块有值)
this.getChildPlate(memo_type,forum_section && forum_section.forum_id);
if (memo_image_info && memo_image_info.id) {
newState.fileList = [{
uid: memo_image_info.id,
name: memo_image_info.filename,
status: 'done',
url: memo_image_info.url,
}]
newState.attachmentData = { // 初始化
name: memo_image_info.filename,
response: {
attachment_id: memo_image_info.id,
}
}
}
this.setState({...newState})
// 加载完后滚动条滚动
window.$("html,body").animate({"scrollTop":0})
// this.props.initForumState({
// current_user,
// tag_list
// })
}
}).catch((error) => {
console.log(error)
})
} else {
this.initMD();
}
}
initMD(initValue, memoId) {
this.contentChanged = false;
const placeholder = "";
// amp;
// 编辑时要传memoId
const imageUrl = `/upload_with_markdown?container_id=${memoId?memoId:''}&container_type=Memo`;
// 创建editorMd
setTimeout(()=>{
const taskpass_editormd = create_editorMD("memoMD", '100%', 400, placeholder, imageUrl, () => {
setTimeout(()=>{
taskpass_editormd.resize()
taskpass_editormd.cm && taskpass_editormd.cm.refresh()
}, 500)
if (initValue) {
taskpass_editormd.setValue(initValue)
}
taskpass_editormd.cm.on("change", (_cm, changeObj) =>{
console.log('....contentChanged')
this.contentChanged = true;
})
});
this.taskpass_editormd = taskpass_editormd;
window.taskpass_editormd = taskpass_editormd;
}, 300)
}
renderOptions(array) {
const elementArray = [];
array.forEach(( item, index ) => {
elementArray.push(
<Option key={index} value={item}>{item}</Option>
)
})
return elementArray
}
onRepertoiresChange(value) {
const index = this.state.repertoires.indexOf(value)
this.setState({
currentSelectRepertoiresIndex: index,
memoRepertoire: value,
memoLanguage: []
});
};
onTagChange(value) {
if (value && value.length > 3) {
this.props.showSnackbar(`最多选择3个技术标签`)
return;
}
this.setState({
memoLanguage: value
})
}
onTypeChange(value) {
this.setState({
memoType: value
})
}
onMemoNameChange(e) {
this.setState({
memoSubject: e.target.value
})
}
renderMemoType() {
const { memo_type } = this.state;
if (!memo_type || memo_type.length === 0) {
return ''
}
const result = []
// memo_type.map((item, index) => <Option value={item.id} key={index} >{item.name}</Option> )
memo_type.forEach((item, index) => {
result.push(<Option value={item.id} key={index} >{item.name}</Option>)
})
return result;
}
renderTag() {
let { memo_tag } = this.state;
if (!memo_tag || memo_tag.length === 0) {
return ''
}
const result = []
memo_tag.forEach((item, index) => {
result.push(<Option value={item.id+''} key={index} >{item.name}</Option>)
})
return result;
}
renderAttachment() {
const { attachments_url } = this.state;
const attachments = []
attachments_url.forEach((item, index) => {
const ar = item.url.split('/')
attachments.push(
<React.Fragment>
<span id={`attachments_10${index}`} className="attachment">
<i className="fa fa-folder mr5 color-light-grey newLeftgrey" aria-hidden="true"></i>
<input type="text" className="upload_filename readonly hidden" name="attachments[2][filename]" readonly="readonly"
style={{border:'none', width:'220px',whiteSpace: 'nowrap', textOverflow:'ellipsis',fontFamily: 'Consolas'}}
size="8" value={item.filename}></input>
<a href={`/attachments/${item.id}.js?attachment_id=10${index}`} className="remove-upload"
style={{verticalAlign: 'top', display: 'inlineBlock'}} data-remote="true"
data-method="delete">
<i className="fa fa-trash-o mr5"></i>
</a>
<div className="div_attachments" name="div_attachments_xx"></div>
<input type="hidden" name="attachments[xx][token]" value="185811.24305bb2c4912f715629aa3615cdbabc"></input>
<input type="hidden" name="attachments[xx][attachment_id]" value={item.id}></input>
</span>
<div className="cl"></div>
</React.Fragment>
)
})
return attachments;
}
_findById(id, arg_items) {
if (!arg_items) {
return undefined
}
const items = arg_items;
for(let i = 0; i < items.length; i++) {
if (id === items[i].id) {
return i;
}
}
}
// 是否是技术动态吧
_isTechShare() {
const { memoType, memo_type } = this.state
const index = this._findById(memoType, memo_type)
return !!index && memo_type[index] && memo_type[index].name.indexOf('技术问答') !== -1
}
onImageUploadDone = (data) => {
this.setState({
attachmentData: data
})
}
onImageUploadRemove = () => {
const { attachment_id } = this.state.attachmentData.response;
const deleteUrl = `/attachments/${attachment_id}.js?attachment_id=1`;
// 获取memo list
axios.delete(deleteUrl, {
withCredentials: true,
})
.then((response) => {
const data = response.data
if (data) {
this.setState({
attachmentData: undefined
})
this.props.showSnackbar('删除成功');
}
}).catch((error) => {
console.log(error)
})
}
// 切换大板块
changeLargeOption=(value)=>{
const { memo_tag } = this.state;
this.getChildPlate(memo_tag,value);
}
getChildPlate =(memo_tag,value)=>{
let listFilter = memo_tag && memo_tag.length > 0 && memo_tag.filter((item=> item.id === value))[0];
let list = listFilter && listFilter.children_tags;
this.setState({
smallOption:list && list.map(item=>{
return(
<Option value={item.id}>{item.title}</Option>
)
}),
bigPlateId:value,
smallPlateId: list && list.length > 0 ? (list[0].id || undefined) : undefined
})
}
// 切换二级板块
changeSmallOption=(value)=>{
this.setState({
smallPlateId:value
})
}
render() {
const { match } = this.props
const {
memoSubject ,
attachments_url,
currentMemoId,
fileList,
memoSubjectLength ,
memo_tag,
smallOption,
bigPlateId,
smallPlateId
} = this.state;
const memoId = match.params.memoId;
console.log("bigPlateId",bigPlateId);
return (
<div className="educontent-min postNewform">
<p className="font-22 color-grey3 mt30 mb10">{ memoId ? '编辑' : '新建'}</p>
<div className="edu-back-white mb10 clearfix" id="memoSubject" style={{position:'relative'}}>
<div className="padding30">
<div className="df">
<span className="mr20 new_label"><span className="color-orange">*</span></span>
<div className="flex1">
<input type="text" className="input-100-45" maxlength="50"
value={memoSubject} onChange={(val)=>this.onMemoNameChange(val)} placeholder="请输入发布标题最大限制50字符">
</input>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl" style={memoSubjectLength==0?{display:'block'}:{display:'none'}}>
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
{/* 发布时间 */}
{/* <div className="df ecSelectbox">
<span className="mr30 color-orange pt10"></span>
<div className="flex1 mr20">
<DatePicker
disabledDate={disabledDate}
showTime
format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择发布时间"
locale={zhCN}
placeholder={"请选择时间"}
onChange={(value, dateString)=>this.onOkTime(value, dateString)}
/>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div> */}
{/* <p className="color-grey-6 font-16 mb30">内容</p> */}
<div className="df mt20">
<span className="mr20 new_label"><span className="color-orange">*</span></span>
<div className="flex1">
<div className="break_word new_li" id="memoMD">
<textarea style={{'display':'none'}}></textarea>
</div>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
<form className="newForm" style={{marginLeft:"70px"}}>
<span id={`attachments_fields`} className="attachments_fields newforgeattachment" xmlns="http://www.w3.org/1999/html">
{ attachments_url && !!attachments_url.length &&
this.renderAttachment()
}
</span>
<span className="add_attachment">
<input className="file_selector" data-are-you-sure="您确定要删除吗?"
data-delete-all-files="您确定要删除所有文件吗" data-description-placeholder="可选的描述"
data-field-is-public="公开" data-file-count="个文件已上传"
data-lebel-file-uploding="个文件正在上传" data-max-concurrent-uploads="2"
data-max-file-size-message="该文件无法上传。超过文件大小限制 (50 MB)建议上传到百度云等其他共享工具里然后在txt文档里给出链接以及共享密码并上传"
data-max-file-size="52428800" data-upload-path="/uploads.js" id="_file"
multiple="multiple" name="attachments[dummy][file]"
onChange={()=>{debugger;window.addInputFiles( window.$('.file_selector')[0] ) }}
style={{'display':'none'}} type="file">
</input>
</span>
</form>
{/* 请求status 422 X-CSRF-Token: eVo38laEF880o3cwZ/0F9kH01q4jMkriuVRemIBq06Y= */}
<div className="df uploadBtn uploadBtnclick mb20" >
<a className="fl" onClick={()=>window.$('#_file').click()}>
<span style={{color: '#21B351', fontSize: "14px", marginLeft: "62px"}}>上传附件</span>
<span style={{color: '#CDCDCD', fontSize: "14px"}}>(单个文件50M以内)</span>
</a>
</div>
{/* 选择了技术问答才显示树标签 */}
{/* this._isTechShare() &&
<div className="edu-back-white mb10 clearfix">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">技术标签</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="flex1 mr20">
<Select
className="ecSelect"
value={memoLanguage}
placeholder="请选择技术标签"
onChange={(e) => this.onTagChange(e)}
dropdownStyle={{'maxHeight': '500px', 'overflow': 'auto'}}
mode="multiple"
tokenSeparators={[';']} >
{this.renderTag()}
</Select>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
</div>
</div> */}
<div className="df ecSelectbox mb20">
<span className="mr20 new_label"><span className="color-orange">*</span></span>
<div className="flex1">
{/* <Select className="ecSelect"
value={memoType}
placeholder="请选择技术标签"
onChange={(val)=>this.onTypeChange(val)}>
{ this.renderMemoType()}
</Select> */}
{/* <Select
mode="multiple"
className="ecSelect"
value={memoLanguage}
placeholder="请选择技术标签"
onChange={(e) => this.onTagChange(e)}
dropdownStyle={{'maxHeight': '500px', 'overflow': 'auto'}}
>
{this.renderTag()}
</Select> */}
<Select className="selectItem" value={bigPlateId} onChange={this.changeLargeOption}>
{
memo_tag && memo_tag.length > 0 && memo_tag.map((item,key)=>{
return(
<Option value={item.id} key={key}>{item.name}</Option>
)
})
}
</Select>
<Select className="selectItem" value={smallPlateId} onChange={this.changeSmallOption}>
{ smallOption }
</Select>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
{/* */}
<div className="df">
<span className="mr20 new_label">上传封面</span>
<div className="flex1 mr20">
<ImageUpload
fileList={fileList}
currentMemoId={currentMemoId}
onImageUploadRemove={this.onImageUploadRemove}
onImageUploadDone={this.onImageUploadDone}
></ImageUpload>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
</div>
</div>
<div className="clearfix mt30 mb50 edu-txt-center">
<span className="inline">
<a onClick={()=>{ this.onCancel() }} className="defalutCancelbtn mr20 fl newdefalutCancelbtn">返回</a>
<a className="defalutSubmitbtn fl newdefalutSubmitbtn" onClick={()=>{this.onCommit()}}>提交</a>
</span>
</div>
</div>
);
}
}
const children = [];
for (let i = 10; i < 36; i++) {
children.push(<Option key={i.toString(36) + i}>{i.toString(36) + i}</Option>);
}
export default MemoNew;

277
src/exchange/Post/New.js Normal file
View File

@ -0,0 +1,277 @@
import React, { PureComponent } from 'react';
import {appendFileSizeToUploadFileAll ,appendFileSizeToUploadFile} from 'educoder';
import { Form , Input , Upload ,Button , Select , Icon , message } from "antd";
import './post.css'
import '../exchange.css'
import axios from 'axios';
import MDEditor from '../../common/MDEditor';
const Option = Select.Option;
const uploadButton = (
<div>
<i className="iconfont icon-tianjia mb30"></i>
<div className="font-16 mb10">点击上传封面</div>
<p className="font-12">只支持JPGPNGJPEG大小不超过2M建议尺寸43</p>
</div>
)
function getBase64(img, callback) {
debugger;
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
class New extends PureComponent{
constructor(props){
super(props);
this.contentMdRef = React.createRef();
this.state={
// 封面图
imageUrl:undefined,
// 大板块下拉选项
memo_type:undefined,
// 小板块
smallOption:undefined,
// 附件列表
contentFileList:undefined,
}
}
componentDidMount = () =>{
this.getInfo();
}
// 获取信息
getInfo = () =>{
let url = '/memos/new';
axios.get((url)).then((result)=>{
if(result){
this.setState({
memo_type:result.data.memo_type
})
}
}).catch((error)=>{
})
}
changeUploadImage =(info)=>{
if (info.file.status === 'done') {
// Get this url from response in real world.
getBase64(info.file.originFileObj, imageUrl =>
this.setState({
imageUrl
}),
);
}
}
// 切换大板块
changeLargeOption=(value)=>{
// console.log(value);
const { memo_type } = this.state;
let list = memo_type.filter((item=> item.id === value))[0].children_tags;
// console.log(list);
this.setState({
smallOption:list && list.map(item=>{
return(
<Option value={item.id}>{item.title}</Option>
)
})
})
}
// 提交
handleSubmit=()=>{
this.props.form.validateFieldsAndScroll((err, values) => {
console.log(values);
if(!err){
let url=`/memos.json`;
axios.post((url),{
forum_id:values.bigPlate,
children_forum_id:values.smallPlate,
memo:{
subject:values.title,
content:values.description
}
}).then(result=>{
if(result){
this.props.showNotification(result.data.message);
this.props.history.push(`/forums/${result.data && result.data.memo_id}`);
}
}).catch(error=>{
})
}
})
}
// 上传附件
handleContentUploadChange = (info) => {
console.log("changeFIle==================>",info);
if (info.file.status === 'done' || info.file.status === 'uploading' || info.file.status === 'removed') {
let contentFileList = info.fileList;
this.setState({ contentFileList: appendFileSizeToUploadFileAll(contentFileList)});
let list = appendFileSizeToUploadFileAll(contentFileList);
let arr = list.map(item=>{
return ( item.response && item.response.id )
})
this.setState({
filesID:arr,
checkFile:arr.length > 0 ? false : true
})
}
}
// 上传附件-删除确认框
onAttachmentRemove = (file, stateName) => {
if(!file.percent || file.percent == 100){
this.props.confirm({
content: '是否确认删除?',
onOk: () => {
this.deleteAttachment(file, stateName)
},
onCancel() {
console.log('Cancel');
},
});
return false;
}
}
deleteAttachment=(file,list)=>{
console.log("delete===============>",file)
}
render(){
const { getFieldDecorator } = this.props.form;
/** 上传封面图 */
let { imageUrl , memo_type , smallOption , contentFileList } = this.state;
const largeOption = memo_type && memo_type.length > 0 && memo_type.map(item=>{
return(
<Option value={item.id}>{item.name}</Option>
)
})
let uploadImg = ( imageUrl ? <img src={imageUrl} alt="avatar" style={{ width: '100%' }} /> : uploadButton )
// 上传封面图
const uploadCover = {
listType:"picture-card",
className:"avatar-uploader",
showUploadList:false,
action:`/uploads.js`,
data: { attachment_id: 1 },
onChange:this.changeUploadImage,
}
/**上传附件 */
const uploadProps = {
width: 600,
fileList: contentFileList,
multiple: true,
action: 'http://127.0.0.1:3000/uploads.js',
onChange: this.handleContentUploadChange,
onRemove: (file) => this.onAttachmentRemove(file, 'contentFileList'),
beforeUpload: (file) => {
console.log('beforeUpload', file.name);
const isLt150M = file.size / 1024 / 1024 < 150;
if (!isLt150M) {
this.props.showNotification('文件大小必须小于150MB!');
}
return isLt150M;
},
};
return(
<div className="educontent-min">
<p className="font-22 color-grey3 mt30 mb10">新建</p>
<div className="bc-white padding30">
<Form className="postNewform">
<Form.Item
label="标题"
>
{getFieldDecorator('title', {
rules: [{
required: true, message: '请输入帖子标题',
},{
max: 5000 , message:'最大限制60个字符'
}],
})(
<Input placeholder="请输入标题最大限制60个字符" maxLength="60" style={{height:"40px"}}/>
)}
</Form.Item>
<Form.Item label="内容" className="editorFromItem">
{getFieldDecorator('description', {
rules: [{
required: true, message: '请输入帖子内容',
},{
max: 5000 , message:'最大限制5000个字符'
}],
})(
<MDEditor ref={this.contentMdRef} placeholder="请输入内容最大限制5000字符" mdID={'courseContentMD'} refreshTimeout={1000}
initValue={""} className="courseMessageMD" ></MDEditor>
)}
</Form.Item>
<Upload {...uploadProps} className="upload_1 newPostUpload">
<Button className="uploadBtn">
<i className="iconfont icon-fabu font-22 color-blue fl mr10"></i>
</Button>
</Upload>
<div className="df mt10">
<Form.Item label="主题板块">
{
getFieldDecorator('bigPlate',{
rules:[{
required:true,message:'请选择大板块'
}]
})(
<Select className="selectItem" onChange={this.changeLargeOption}>
{ largeOption }
</Select>
)
}
</Form.Item>
<Form.Item label="">
{
getFieldDecorator('smallPlate',{
rules:[{
required:true,message:'请选择小板块'
}]
})(
<Select className="selectItem">
{ smallOption }
</Select>
)
}
</Form.Item>
</div>
<div className="df uploadImageBox">
<label className="new_label">上传封面</label>
<Upload {...uploadCover}>
{uploadImg}
</Upload>
</div>
</Form>
</div>
<p className="clearfix mt20 mb50 edu-txt-center">
<span className="inline">
<a className="defalutCancelbtn fl mr20">取消</a>
<Button type="primary" onClick={this.handleSubmit} className="defalutSubmitbtn fl mr20">提交</Button>
</span>
</p>
</div>
)
}
}
const WrappedNewPostForm = Form.create({ name: 'New' })(New);
export default WrappedNewPostForm;

View File

@ -0,0 +1,33 @@
import React, { PureComponent } from 'react';
class Parse extends PureComponent{
render(){
let { judge , num , clickEvent ,current_user} = this.props;
const info = (
current_user? (judge ?
<a className={`forumParse`} onClick={clickEvent}>
<i className="iconfont icon-dianzan font-24" style={{height:"30px",lineHeight:"40px"}}></i>
<span>{num}</span>
</a>
:
<span className={`forumParse parsed`} >
<i className="iconfont icon-dianzan font-24" style={{height:"30px",lineHeight:"40px"}}></i>
<span>{num}</span>
</span>
):
<a className={`forumParse`} href="/login">
<i className="iconfont icon-dianzan font-24" style={{height:"30px",lineHeight:"40px"}}></i>
<span>{num}</span>
</a>
)
return(
<React.Fragment>
{ info }
</React.Fragment>
)
}
}
export default Parse;

157
src/exchange/Post/post.css Normal file
View File

@ -0,0 +1,157 @@
.postDetail{
padding:20px 0px;
}
.postContent{
margin:0px 30px;
border-top: 1px solid #f4f4f4;
}
.fileTeam{
padding-bottom: 40px;
}
.fileTeam > li {
height: 18px;
line-height: 18px;
margin-bottom: 10px;
}
/* parse.js */
.forumParse{
width: 72px;
height: 72px;
display: inline-flex;
align-items: center;
flex-flow: column;
background: #5091FF;
color: #fff!important;
border-radius: 50%;
padding-top: 7px;
}
.forumParse.parsed{
background:#999999;
}
/* 评论 */
.replyTitle{
line-height: 30px;
padding:20px;
border-bottom: 1px solid #f4f4f4;
}
.pre_stage{
border-bottom: 1px solid #f4f4f4;
padding : 10px 0px;
}
.pre_stage:last-child{
border-bottom: none;
}
.pre_stage .sub_stage{
padding: 10px 20px;
box-sizing: border-box;
background: #fafafa;
position: relative;
margin-top: 20px;
border-radius:4px;
}
.pre_stage .sub_stage::before{
position: absolute;
content: "";
top:-20px;
z-index: 1;
left: 20px;
width: 0;
height: 0;
border-width: 10px;
border-style: solid;
border-color: transparent transparent #fafafa transparent;
}
.commentsItem_infos{
display: flex;
padding:10px 0px 10px;
}
.commentsItem_infos .markdown-body{
padding:0px;
}
.commentsItem_infos .editormd-html-preview,.commentsItem_infos .editormd-preview-container{
background-color: unset;
}
/* 新建 */
.postNewform.ant-form .ant-row{
display: flex;
align-items: top;
}
.postNewform.ant-form .ant-form-item-control-wrapper{
flex: 1;
}
.postNewform.ant-form .ant-form-item-label,.postNewform .new_label{
width: 90px;
text-align: right;
margin-right: 8px;
height: 40px;
line-height: 40px;
margin-top: 3px;
font-size: 16px
}
.postNewform.ant-form label{
font-size: 16px;
color: #333;
}
.editorFromItem {
align-items: flex-start!important;
margin-bottom: 0px!important;
}
.editorFromItem .editormd.editormd-vertical{
margin-bottom: 0px;
}
.editorFromItem .rememberTip{
height: 20px;
line-height: 20px;
}
.newPostUpload{
margin-left: 84px;
}
.newPostUpload .uploadBtn{
border:none;
box-shadow: none;
color:#5091FF;
line-height: 30px;
}
.newPostUpload .ant-upload-list{
padding-left: 100px;
}
.newPostUpload .ant-upload-list-item-info{
padding:0px;
font-size: 15px;
height: 24px;
line-height: 24px;
max-width: 500px;
position: relative;
}
.selectItem .ant-select-selection--single{
width: 200px;
margin-right: 20px;
height: 40px;
}
.selectItem .ant-select-selection__rendered{
line-height: 40px;
}
/* 上传封面 */
.uploadImageBox .ant-upload-picture-card-wrapper{
width: 340px;
height: 248px;
border:none;
background: #fafafa;
display: flex;
flex-direction: column;
padding:0px 60px;
color: #999;
}
.uploadImageBox .ant-upload.ant-upload-select-picture-card{
width: 100%;
height: 100%;
border: none;
}
.uploadImageBox .ant-upload.ant-upload-select-picture-card i{
font-size: 44px!important;
color:#999;
}

View File

@ -0,0 +1,16 @@
import React, { PureComponent } from 'react';
class Index extends PureComponent {
render(){
const {count, returnEvent} = this.props;
return(
<p className="clearfix pl30 pr30 mb10">
<span className="fl color-grey3 font-16">共找到相关结果<span className="color-blue">{count}</span></span>
<span onClick={returnEvent} className="color-grey-6 fr mt2 c_point">返回</span>
</p>
)
}
}
export default Index;

View File

@ -0,0 +1,18 @@
import React, { PureComponent } from 'react';
import { getImageUrl } from 'educoder';
class Index extends PureComponent {
render(){
let { topClass , bestClass } = this.props;
return(
<React.Fragment>
{ topClass && <img alt="" src={getImageUrl("images/plate/top.png")} width="42px" className={ topClass }></img> }
{ bestClass && <img alt="" src={getImageUrl("images/plate/best.png")} width="28px" className={ bestClass }></img> }
</React.Fragment>
)
}
}
export default Index;

289
src/exchange/exchange.css Normal file
View File

@ -0,0 +1,289 @@
.newContainer{
padding:60px 0px 120px 0px;
}
ul,p{
margin: 0px;
}
.flex1{
flex: 1;
width: 0
}
.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-nav .ant-tabs-tab:hover{
color:#21B350!important;
}
/* shownotification的z-index */
.ant-notification{
z-index: 100000;
}
/* 小按钮 */
.small-default-btn{
height: 24px;
line-height: 24px;
padding: 0px 15px;
font-size: 12px;
background:rgba(244,244,244,1);
border-radius:2px;
color: #999;
display: inline-block;
}
.middle-default-btn{
height: 30px;
line-height: 30px;
padding: 0px 15px;
font-size: 12px;
background:rgba(244,244,244,1);
border-radius:2px;
color: #999;
display: inline-block;
}
.small-blue-btn{
background:rgba(80,145,255,1);
color:rgba(255,255,255,1);
}
.small-green-btn{
background:#21B350;
color:rgba(255,255,255,1)!important;
}
.educontent{
width: 1200px;
margin:0px auto;
}
.educontent-min{
width: 960px;
margin:0px auto;
}
.F_panel{
width: 1200px;
display: flex;
margin:30px auto;
}
.f_left_head{
display: flex;
justify-content:space-between;
border-bottom: 1px solid #eaeaea;
padding-left:30px;
background: #fff;
}
.f_left_head > ul{
display: flex;
align-items: center;
height:80px;
margin-bottom: 0px;
}
.f_left_head > ul >li{
margin-right: 30px;
color: #666666
}
.f_left_head > ul >li a{
color: #666666
}
.f_left_head > ul:first-child > li{
font-size: 16px;
position: relative;
color: #333;
}
.f_left_head > ul:first-child > li.active a{
color: #5091FF;
}
.f_left_head > ul:first-child > li.active a:after{
position: absolute;
width: 100%;
bottom: -25px;
height: 2px;
background: #5091FF;
content: '';
left: 0px;
}
.platePanel{
top: 24px;
background: #fff;
left: 0px;
box-shadow:0px 6px 16px 0px rgba(7,12,70,0.2);
border-radius:4px;
max-width: 770px;
max-height: 500px;
padding:10px 10px 0px 10px;
overflow: auto;
box-sizing: border-box;
}
.platePanel .plateItem{
display: flex;
padding-bottom:10px;
box-sizing: border-box;
}
.platePanel .plateItem .plateItem_h{
color: #333;
margin:5px 20px 0px 10px;
}
.plateUl{
flex: 1;
}
.plateUl li{
position: relative;
margin-right: 11px;
color: #666;
float: left;
padding:5px 0px;
box-sizing: border-box;
margin-left: 10px;
}
.plateUl li:after{
width: 1px;
position: absolute;
right: -12px;
height:10px;
background: #ccc;
top:11px;
content: ''
}
.plateUl li:last-child:after{
display:none;
}
.radius{
border-radius: 50%;
}
.flex1{
flex: 1;
}
.color-blue {
color: #5091FF!important;
}
.flex-align-center{
display: flex;
align-items: center;
}
.flex-align-top{
display: flex;
align-items: flex-start;
}
.flex-align-bottom{
display: flex;
align-items: flex-end;
}
/* 列表 */
.plateTabulation{
padding: 0px 30px;
box-sizing: border-box;
background: #fff;
}
.plateTabulation > li{
border-bottom: 1px solid #f4f4f4;
padding:20px 0px;
}
.plateTabulation > li:last-child{
border-bottom: none;
}
.exchangeItem-subject{
font-size: 16px;
color: #333;
flex:1;
height: 20px;
line-height: 20px;
margin-right: 20px;
}
.flex_h{
display: flex;
align-items: center
}
.sendPoint{
font-size: 12px;
color: #999;
position: relative;
margin-left: 20px;
height: 18px;
line-height: 18px;
}
.sendPoint::before{
position: absolute;
left: -10px;
top:3px;
height:12px;
width:1px ;
content: '';
background: #ccc;
}
p{
margin:0px;padding:0px
}
.edu-txt-center .ant-dropdown-menu-item,.edu-txt-center .ant-dropdown-menu-submenu-title{
text-align: center;
}
.c_point{
cursor: pointer;
}
.top_Operate{
position: relative;
background: #fff;
padding:20px;
margin-bottom: 20px;
}
.top_Operate .send_btn{
width: 188px;
height: 40px;
background: #5091ff;
text-align: center;
color: #fff;
font-size: 16px;
line-height: 40px;
display: block;
border-radius: 4px;
}
.top_Operate .searchfrom{
position: absolute;
width: 40px;
right:20px;
top:20px;
}
.top_Operate .searchfrom.ant-input-affix-wrapper .ant-input-suffix{
right:-2px
}
.top_Operate .searchfrom .ant-input-search-icon svg{
width: 3em;
height: 1.3em;
}
.top_Operate .searchfrom.active{
width: 250px;
z-index: 10;
}
.r_part_title{
border-bottom: 1px solid #F4F4F4;
padding:20px;
}
.r_part_list{
padding:20px;
}
.r_part_list li{
margin-bottom: 20px;
color: #333;
}
.r_part_list li:last-child{
margin-bottom: 0px;
}
/* InfoComponent */
.icon-wrap{
margin-left: 30px;
color: #ccc;
float: left;
line-height: 20px;
}
.icon-wrap > i{
margin-right: 5px;
float: left;
}
/*給下拉列表的……添加高宽*/
.addheight{
background: url(./images/more.png) no-repeat right;
width: 30px;
height: 20px;
}
/*帖子列表标题点击变颜色*/
.is_onclick:hover{
color: #5091FF;
}
/*帖子详情标题颜色不变*/
.color_black{
color: black;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

98
src/exchange/index.js Normal file
View File

@ -0,0 +1,98 @@
import React, { Component } from 'react';
import { Route , Switch } from 'react-router-dom';
import { TPMIndexHOC } from '../modules/tpm/TPMIndexHOC';
import { SnackbarHOC , CNotificationHOC } from 'educoder';
import ExchangeIndex from './ExchangeIndex';
import Post from './Post/Index'
import PostNew from './Post/MemoNew';
import PlateIndex from './Plate/Index';
import MyExchange from './MyExchange/Index'
import PreManager from './Manage/PreModerator'
import axios from 'axios';
import './exchange.css';
class Index extends Component{
constructor(props){
super(props);
this.state={
current_user:undefined
}
}
componentDidMount=()=>{
const url = `/users/get_user_info.json`;
axios.get(url).then(result=>{
if(result){
this.setState({
current_user:result.data.current_user
})
// 给NewHeader传值当前用户的信息
if(result.data.current_user && result.data.current_user.login){
this.props.initCommonState(result.data.current_user);
}
}
}).catch(error=>{
})
}
componentDidUpdate=()=>{
this.props.history.listen(()=>{
if (document.body.scrollTop || document.documentElement.scrollTop > 0) {
window.scrollTo(0, 0)
}
})
}
render(){
return(
<Switch {...this.props}>
{/* 帖子列表 */}
<Route path='/forums/plates/:plateid/all' render={
(props) => (<PlateIndex {...this.props} {...this.state} {...props} />)
} ></Route>
{/* 编辑帖子 */}
<Route path={`/forums/:memoId/edit`} render={
(props) => {
return (<PostNew {...this.props} {...this.state} {...props}></PostNew>)}
}></Route>
{/* 新建帖子 */}
<Route path={`/forums/new`} render={
(props) => {
return (<PostNew {...this.props} {...this.state} {...props}></PostNew>)}
}></Route>
{/* 版主管理 */}
<Route path={`/forums/manage/:plateId`} render={
(props) => {
return (<PreManager {...this.props} {...this.state} {...props}></PreManager>)}
}></Route>
{/* 我的话题 */}
<Route path={`/forums/MyTopic`} render={
(props) => {
return (<MyExchange {...this.props} {...this.state} {...props}></MyExchange>)}
}></Route>
<Route path={`/forums/MyEnshrine`} render={
(props) => {
return (<MyExchange {...this.props} {...this.state} {...props}></MyExchange>)}
}></Route>
<Route path={`/forums/MyInteresting`} render={
(props) => {
return (<MyExchange {...this.props} {...this.state} {...props}></MyExchange>)}
}></Route>
{/* 帖子详情 */}
<Route path={`/forums/:postid`} render={
(props) => {
return (<Post {...this.props} {...this.state} {...props}></Post>)}
}></Route>
{/* 论坛首页 */}
<Route path={`/forums`} render={
(props) => (<ExchangeIndex {...this.props} {...this.state} {...props} />)
}></Route>
</Switch>
)
}
}
export default SnackbarHOC() (CNotificationHOC() (TPMIndexHOC ( Index ))) ;

View File

@ -38,7 +38,7 @@ class Index extends Component {
return (
<div className="newMain clearfix">
<Handbook />
<Switch {...this.props}>
{/* <Switch {...this.props}>
<Route
path="/projects/:projectsType/new"
render={(props) => (
@ -67,7 +67,7 @@ class Index extends Component {
<ProjectIndex {...this.props} {...props} />
)}
></Route>
</Switch>
</Switch> */}
</div>
);
}

View File

@ -244,7 +244,7 @@ class Detail extends Component {
this.setState({
projectDetail: result.data,
project_id: result.data.project_id,
isManager: result.data.permission && result.data.permission === "Manager",
isManager: result.data.permission && (result.data.permission === "Manager" || result.data.permission === "Admin" || result.data.permission === "Owner"),
isReporter: result.data.permission && result.data.permission === "Reporter",
isDeveloper: result.data.permission && result.data.permission === "Developer",
http_url: result.data.clone_url,

70
src/forge/Upload/Cover.js Normal file
View File

@ -0,0 +1,70 @@
import React, { useState, useEffect } from 'react';
import { Upload, Spin, message } from 'antd';
import { getUploadActionUrl } from 'educoder';
export default (({ imageUrl, getImageUrl }) => {
const [loading, setLoading] = useState(false);
const [url, setUrl] = useState(undefined);
useEffect(() => {
if (imageUrl) {
setUrl(imageUrl);
}
}, [imageUrl])
function getBase64(img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
function handleChange(info) {
if (info.file.status === 'uploading') {
setLoading(true);
return;
}
if (info.file.status === 'done') {
let filelist = info.fileList && info.fileList.length>0 && info.fileList[info.fileList.length-1];
if(filelist && filelist.response && filelist.response.status === -1){
setLoading(false)
setUrl(null)
message.error(filelist.response.message)
}else{
getBase64(info.file.originFileObj, imageUrl =>{
setLoading(false),
getImageUrl(filelist && filelist.response),
setUrl(imageUrl)
});
}
}
};
function beforeUpload(file) {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('只能上传图片!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('图片大小必须小于2MB!');
}
return isJpgOrPng && isLt2M;
}
const uploadButton = (
<div>
{loading ? <Spin size={"small"} /> : <i className="iconfont icon-tianjiadaohang font-20" />}
</div>
);
return (
<Upload
name="file"
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action={getUploadActionUrl()}
beforeUpload={beforeUpload}
onChange={handleChange}
>
{url ? <img src={url} alt="avatar" style={{ width: '100%' }} /> : uploadButton}
</Upload>
)
})

View File

@ -0,0 +1,130 @@
import React, { useState, useEffect } from "react";
import { WhiteBack } from "../css/layout";
import Title from "./Title";
import { Modal, Button, Input , notification, message } from "antd";
import Nodata from "../Nodata";
import axios from 'axios';
const TextArea = Input.TextArea;
export default (({ content , operation ,plateId }) => {
const [word, setWord] = useState(undefined);
const [editWord, setEditWord] = useState(undefined);
const [show, setShow] = useState(false);
const [visible, setVisible] = useState(false);
const [AnnModalType, setAnnModalType] = useState(1); //1
let child = document.getElementById("annWords") && document.getElementById("annWords").offsetHeight;
useEffect(() => {
if (content) {
setWord(content.notice);
}
}, [content]);
useEffect(() => {
changeShow();
}, [word, child , content]);
//
function saveAnn() {
setWord(editWord);
setVisible(false);
//
if(editWord){
const url = `/forum_sections/${plateId}/edit_notice.json`;
axios.post(url,{
content:editWord
}).then(result=>{
if(result && result.data){
notification.open({message:"提示",description:result.data.message});
changeShow();
}
}).catch(error=>{
console.log(error);
})
}
}
function changeShow(){
let p = document.getElementById("annContent")&& document.getElementById("annContent").offsetHeight;
let c = document.getElementById("annWords") &&document.getElementById("annWords").offsetHeight;
if (c > p) {
setShow(true);
}else{
setShow(false);
}
}
//
function cancelAnn() {
setEditWord(word);
setVisible(false);
}
// (1)(2)
function changeAnnModalType(type) {
setAnnModalType(type);
setVisible(true);
setEditWord(word);
}
function changeText(e) {
setEditWord(e.target.value);
}
return (
<WhiteBack>
<Modal
visible={visible}
title="公告"
closable={true}
onCancel={()=>setVisible(false)}
footer={
AnnModalType === 1 ? false : <div>
<Button onClick={cancelAnn}>取消</Button>
<Button onClick={saveAnn} type={"primary"}>
发布
</Button>
</div>
}
>
{AnnModalType === 1 ? (
<p style={{ maxHeight: "100px", overflowY: "auto" }}>{word}</p>
) : (
<TextArea
placeholder="填写公告1600字"
value={editWord}
rows={5}
onChange={changeText}
/>
)}
</Modal>
<Title>
<span>公告</span>
{
operation ?
<a onClick={() => changeAnnModalType(2)}>
<i className="iconfont icon-bianji3 grey-9"></i>
</a>:""
}
</Title>
<div style={{ padding: "10px 30px" }} className="pr">
{word ? (
<React.Fragment>
<div id="annContent" className="annContent">
<p id="annWords" className="annWords">
{word}
</p>
</div>
<span className="grey-8">版主{content.name}</span>
</React.Fragment>
) : (
<Nodata _html={"暂无公告"} />
)}
{
word && show === true ?
<a className="annBtn" onClick={() => changeAnnModalType(1)}>
<span className="green ml4">查看</span>
</a> : ""
}
</div>
</WhiteBack>
);
});

View File

@ -0,0 +1,51 @@
import React , { useState, useEffect } from 'react';
import { WhiteBack , Greenline , UDStructure , Grid } from '../css/layout';
import UserInfo from './UserInfo';
import { getUrl } from "educoder";
import StarUser from "../../user_info/User/StarUser";
export default (({user})=>{
useEffect(() => {
if(user){
setFansCount(user.watchers_count)
}
},[]);
const [fansCount, setFansCount] = useState(0)
const fans_count = (count) => {
let new_fans_count = fansCount + count
setFansCount(new_fans_count)
}
return(
<WhiteBack className="authorCard">
<UserInfo url={getUrl(user && user.image_url)} name={user && user.username} login={user && user.login} column/>
<p style={{width:"100%"}} className="task-hide grey-8 mt10 mb15 edu-text-center">{user && user.description ? user.description : "这家伙太懒了,还未填写个人描述!"}</p>
{/* {user && !user.is_current_user && (
<StarUser
current_login={user.current_login}
login={user.login}
user_id={user.user_id}
is_watched={user.watched}
is_blocked={user.is_blocked}
user_name={user.username}
is_blocked_by={user.is_blocked_by}
set_fans_count={fans_count}
show_block={false}
></StarUser>
)} */}
<Grid className="mt20">
<UDStructure>
<span>{user && user.memos_count}</span>
<span>文章数</span>
</UDStructure>
<UDStructure>
<span>{user && user.replies_count}</span>
<span>评论数</span>
</UDStructure>
<UDStructure>
<span>{fansCount}</span>
<span>关注者</span>
</UDStructure>
</Grid>
</WhiteBack>
)
})

View File

@ -0,0 +1,23 @@
import React, { useState } from "react";
import { WhiteBack } from "../css/layout";
import Title from './Title';
import UserInfo from "./UserInfo";
import { getUrl } from 'educoder';
export default (({ author }) => {
return (
<WhiteBack>
<Title>版块活跃作者</Title>
<ul className="authorUl">
{
author && author.length > 0 ?
author.map((item,key)=>{
return(
<UserInfo login={item.login} url={getUrl(`/images/${item.image_url}`)} name={item.username} column/>
)
})
:""
}
</ul>
</WhiteBack>
);
});

View File

@ -0,0 +1,27 @@
import React,{useEffect,useState} from 'react';
import {Link} from 'react-router-dom';
import Nodata from '../Nodata';
export default (({ memos , selectKey })=>{
const [list, setList] = useState(undefined);
const [ keys , setKeys ] = useState(undefined);
useEffect(() => {
if (memos) {
setList(memos);
setKeys(selectKey[0]);
}
}, [memos , selectKey]);
return(
<ul className="BestUl">
{
list && list.length>0 ? list.map((item,key)=>{
return(
<li className="task-hide"><Link to={`/forums/${item && item.id}/detail`}>{item && item.subject}</Link></li>
)
}):
<Nodata _html={`暂无${keys === "hot" ? "话题":"推荐"}~`} />
}
</ul>
)
})

View File

@ -0,0 +1,54 @@
import React, { useState } from "react";
import { WhiteBack, AlignCenter } from "../css/layout";
import { Link } from 'react-router-dom';
import Title from "./Title";
import ring from "../image/radius.png";
import styled from "styled-components";
const Img = styled.img`
{
width: 40px;
height: 40px;
margin-right: 14px;
border-radius: 50%;
}
`;
const Prag = styled.p`
{
color: #999;
font-size: 14px;
line-height: 18px;
margin: 4px 0 !important;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
}
`;
export default ({ recommand }) => {
return (
<WhiteBack>
<Title>精选板块</Title>
<ul className="BestModalUl">
{recommand && recommand.length > 0
? recommand.map((item, key) => {
return (
<AlignCenter>
<Img src={item.picture || ring} />
<div className="flex1" style={{ width: "0" }}>
<Link to={`/forums/theme/${item.id}`} className="grey-3">{item.title}</Link>
<Prag>{item.description || "暂无描述~"}</Prag>
{/* <p className="task-hide">{item.description}</p> */}
<span className="font-12 grey-9">{item.watchers_count}人收藏<span className="ml15">{item.memos_count}个话题</span></span>
</div>
</AlignCenter>
);
})
: ""}
</ul>
</WhiteBack>
);
};

View File

@ -0,0 +1,66 @@
import React, { useState, useEffect } from "react";
import logo from "../image/radius.png";
import { FlexAJ, P } from "../css/layout";
import { Link } from "react-router-dom";
import styled from "styled-components";
const Img = styled.img`
{
width: 60px;
height: 60px;
border-radius: 50%;
margin-right: 25px;
}
`;
const Prag = styled.p`
{
color: #333;
font-size: 14px;
line-height: 18px;
margin-bottom: 10px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
height:18px;
}
`;
export default (props) => {
const list = props.plate;
return (
<div className="doubleItems">
{list && list.length > 0
? list.map((item, key) => {
return (
<Link
to={`/forums/theme/${item.id}`}
style={{
borderBottom:
key === list.length - 2 && list.length % 2 === 0
? "none"
: "",
}}
>
<Img src={item && item.picture ? item.picture : logo} />
<div className="flex1"style={{width:"0"}}>
<div style={{display:"flex"}}>
<P style={{maxWidth:"370px"}}>{item.title}</P>
<span className="ml15" style={{fontSize:"14px",color:"#999"}}>帖子数{item.memos_count}</span>
</div>
<Prag>{item.description || <span className="grey-9">暂无描述~</span>}</Prag>
<FlexAJ style={{ fontSize: "12px" }}>
<span>版主{item.user_name}</span>
</FlexAJ>
</div>
</Link>
);
})
: ""}
</div>
);
};

View File

@ -0,0 +1,114 @@
import React from "react";
import { Menu , Dropdown , notification } from "antd";
import {Link} from 'react-router-dom';
import axios from 'axios';
// permission = {
// admin://
// banned_permission//
// is_currentUser: true, #/
// is_fine: true, #
// sticky: true, #
// memo_watched: true, #
// is_deleted:true#
// }
export default ({ id , permission , calbackFunc , confirm }) => {
//
function changeSticky(s){
let sticky = s ? 0 : 1;//10
const url = `/memos/${id}/set_top_or_down.json`;
axios.get(url,{
params:{
sticky
}
}).then(result=>{
if(result){
notification.open({message:"提示",description:result.data.message});
calbackFunc && calbackFunc();
}
})
}
//
function changeFine(f){
let is_fine = f ? 0 : 1;//10
const url = `/memos/${id}/is_fine.json`;
axios.post(url,{
is_fine
}).then(result=>{
if(result){
notification.open({message:"提示",description:result.data.message});
calbackFunc && calbackFunc();
}
})
}
//
function changeMemoWatched(m){
let is_watch = m ? 0 : 1;//10
const url = `/memos/${id}/watch_memo.json`;
axios.post(url,{
is_watch
}).then(result=>{
if(result){
notification.open({message:"提示",description:result.data.message});
calbackFunc && calbackFunc();
}
})
}
//
function deleteForum(){
confirm && confirm({
content: '确认删除帖子?',
onOk:()=>{
const url = `/memos/${id}.json`;
axios.delete(url).then(result=>{
if(result){
notification.open({message:"提示",description:result.data.message});
calbackFunc && calbackFunc();
window.location.href="/forums"
}
})
}
})
}
//
function sendDeleteForum(d){
let is_apply = d ? 0 : 1;//10
confirm && confirm({
content: '确认申请删帖?',
onOk:()=>{
const url = `/memos/${id}/confirm_delete.json`;
axios.post(url,{
is_apply
}).then(result=>{
if(result){
notification.open({message:"提示",description:result.data.message});
calbackFunc && calbackFunc();
}
})
}
})
}
const menu=(
permission &&
<Menu style={{minWidth:"100px",textAlign:'center'}}>
{ permission.banned_permission && <Menu.Item onClick={()=>changeSticky(permission.sticky)}>{permission.sticky ? "取消置顶":"置顶"}</Menu.Item>}
{ permission.banned_permission && <Menu.Item onClick={()=>changeFine(permission.is_fine)}>{permission.is_fine ? "取消推荐":"推荐"}</Menu.Item>}
{ permission.login && <Menu.Item onClick={()=>changeMemoWatched(permission.memo_watched)}>{permission.memo_watched ? "取消收藏":"收藏"}</Menu.Item> }
{ (permission.admin || permission.is_currentUser) && <Menu.Item><Link to={`/forums/${id}/edit`}>编辑</Link></Menu.Item>}
{ permission.admin ?
<Menu.Item onClick={()=>deleteForum()}>删除</Menu.Item>
:
permission.is_currentUser ?
<Menu.Item onClick={()=>sendDeleteForum(permission.is_deleted)}>{permission.is_deleted?"撤销申请":"申请删帖"}</Menu.Item>:""
}
</Menu>
)
return (
<Dropdown overlay={menu} align={"center"} placement={"bottomCenter"}>
<i className="iconfont icon-gengduo1"></i>
</Dropdown>
);
};

View File

@ -0,0 +1,19 @@
import React from 'react';
export function Type(value){
if(value === "交流"){
return (<span className="blue">交流</span>)
}else if(value === "求助"){
return (<span className="orange">求助</span>)
}
}
export function Tag(value){
if(value === "置顶"){
return (<span className="tag tagRed">置顶</span>)
}else if(value === "精华"){
return (<span className="tag tagBlue">精华</span>)
}else if(value === "原创"){
return (<span className="tag tagOrange">原创</span>)
}
}

View File

@ -0,0 +1,89 @@
import React, { useState, useEffect } from "react";
import { FlexAJ, AlignCenter, LeftLine } from "../css/layout";
import { Link } from "react-router-dom";
import { getUrl } from 'educoder';
import { Type, Tag } from "./ItemType";
import User from "./User";
import Drop from "./ItemDropDown";
import Nodata from '../Nodata';
export default ({ memos , current_user , calbackFunc , confirm }) => {
const [list, setList] = useState(undefined);
useEffect(() => {
if (memos) {
setList(memos);
}
}, [memos]);
return (
<ul className="forumList">
{list && list.length > 0 ? (
list.map((item, key) => {
let permission = {
index:key,
admin:current_user && current_user.admin,
banned_permission:item.banned_permission,
login:current_user && current_user.login,
is_currentUser:current_user && (item.user_login === current_user.login),
is_fine:item.is_fine,
sticky:item.sticky,
memo_watched:item.memo_watched,
user_banned_permission:item.banned_permission,
is_deleted:item.apply_destroy
}
return (
<li>
<FlexAJ>
<AlignCenter style={{ marginLeft: "-8px" }}>
<span>{Type(item.tag_name)}</span>
<Link
className="grey-3 task-hide"
style={{ maxWidth: "700px" }}
to={`/forums/${item.id}/detail`}
>
{item.subject}
</Link>
{ item.sticky === true ? <span className="ml8">{Tag("置顶")}</span> : "" }
{ item.is_original === true ? <span className="ml8">{Tag("原创")}</span> : "" }
{ item.is_fine === true ? <span className="ml8">{Tag("精华")}</span> : "" }
</AlignCenter>
<AlignCenter>
{ item.apply_destroy ? <span className="orange font-12 mr10">已申请删帖</span> : "" }
{
current_user && current_user.login ?
<Drop permission={permission} id={item.id} calbackFunc={calbackFunc} confirm={confirm}/>
:""
}
</AlignCenter>
</FlexAJ>
<FlexAJ className="mt8">
<AlignCenter>
<User login={item.user_login} name={item.username} url={getUrl("/images/"+item.image_url)}></User>
{item.forum_section_title ? <Link to={`/forums/theme/${item.forum_section_id}`}><LeftLine>{item.forum_section_title}</LeftLine></Link> : "" }
{item.published_time ? <LeftLine>{item.published_time}</LeftLine> : "" }
</AlignCenter>
<span>
<span class="icon-wrap">
<i class="iconfont icon-zhengyan font-18"></i>
<span class="span-text">{item.viewed_count}</span>
</span>
<span class="icon-wrap">
<i class="iconfont icon-dianzan2 font-14"></i>
<span class="span-text">{item.praises_count}</span>
</span>
<span class="icon-wrap">
<i class="iconfont icon-pinglun1 font-14"></i>
<span class="span-text">{item.replies_count}</span>
</span>
</span>
</FlexAJ>
</li>
);
})
) :
<AlignCenter style={{height:"400px"}} className="bigNoData"><Nodata _html="暂无数据" /></AlignCenter>
}
</ul>
);
};

View File

@ -0,0 +1,22 @@
import React from 'react';
import { Input } from 'antd';
import { Link } from 'react-router-dom';
const Search = Input.Search;
export default (({ onSearch , current_user , showLoginDialog })=>{
return(
<React.Fragment>
<Search onSearch={onSearch} placeholder="搜索" allowClear/>
{
current_user && current_user.login ?
<Link className="greenbtn" to={`/forums/new`} style={{width:"100%",marginTop:"20px"}}>
<i className="iconfont icon-bianjishijuan3x" style={{marginRight:"5px"}}></i>写点什么
</Link>
:
<a className="greenbtn" onClick={showLoginDialog} style={{width:"100%",marginTop:"20px"}}>
<i className="iconfont icon-bianjishijuan3x" style={{marginRight:"5px"}}></i>写点什么
</a>
}
</React.Fragment>
)
})

View File

@ -0,0 +1,17 @@
import React from "react";
import { Pagination } from "antd";
export default (({page,total,changePage , pageSize}) => {
return (
total > pageSize ?
<div className="center" style={{ padding: "25px 0px" }}>
<Pagination
showQuickJumper
current={page}
onChange={changePage}
total={total}
pageSize={pageSize}
/>
</div>:""
);
});

View File

@ -0,0 +1,47 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { message } from "antd";
export default ({ isPrised, num, memo_id, container_type, current_login, props }) => {
const [flag, setFlag] = useState(undefined);
const [number, setNumber] = useState(undefined);
useEffect(() => {
setFlag(isPrised);
setNumber(num);
}, []);
function priseForums() {
if (current_login) {
axios
.post(`/memos/${memo_id}/plus.json`, {
container_type: container_type,
id: memo_id,
type: flag ? 0 : 1,
})
.then((result) => {
setNumber(result.data.praise_count);
setFlag(!flag);
message.success(flag ? "取消点赞" : "已点赞")
})
.catch((error) => {
message.error(error);
});
} else {
props.showLoginDialog();
return;
// window.open("/login", "_blank");
}
}
return (
<div className="priseBox">
<span onClick={priseForums}>
<i
className={
flag ? "iconfont icon-dianzan" : "iconfont icon-dianzan-xian"
}
></i>
</span>
<span>{number}</span>
</div>
);
};

View File

@ -0,0 +1,19 @@
import React from 'react';
import styled from 'styled-components';
const Nav = styled.div`{
background-color:#fff;
padding:20px 30px;
border-bottom:1px solid #eee;
font-size:16px;
color:#333;
display:flex;
justify-content: space-between;
align-items:center;
}`
export default (({children,className})=>{
return(
<Nav className={className}>{children}</Nav>
)
})

View File

@ -0,0 +1,152 @@
import React, { useEffect , useState } from "react";
import { Button , notification } from "antd";
import { WhiteBack, FlexStart, P, FlexAJ, AlignCenter } from "../css/layout";
import Radius from "../image/radius.png";
import styled from "styled-components";
import axios from "axios";
import {Link} from 'react-router-dom';
const Img = styled.img`
{
width: 60px;
height: 60px;
border-radius: 50%;
margin-right: 15px;
}
`;
const Prag = styled.p`
{
color: #333;
font-size: 14px;
line-height: 18px;
margin: 10px 0px;
text-align: justify;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
word-break: break-all;
max-height: 54px;
}
`;
const Spanleft = styled.span`
{
margin-right: 30px;
color: #888;
font-size: 12px;
}
`;
const Spanright = styled.span`
{
margin-left: 30px;
color: #888;
font-size: 12px;
& > label {
color: #333;
}
}
`;
export default ({ headData, title , operation , history }) => {
const [section, setSection] = useState(undefined);
const [sectionUser, setSectionUser] = useState(undefined);
const [forumModers, setForumModers] = useState(undefined);
const [ watched , setWacth ] = useState(headData && headData.watched);
useEffect(() => {
if (headData) {
setSection(headData.forum_section);
setSectionUser(headData.forum_section_user);
setForumModers(headData.forum_moders);
setWacth(headData.watched);
}
}, [headData]);
//
function saveForum(id){
if(id){
const url = `/forum_memos/${id}/is_watch.json`;
axios.post(url,{
is_watch:watched?0:1
}).then(result=>{
if(result && result.data && result.data.status!=-1){
setWacth(!watched);
notification.open({message:"提示",description:result.data.message});
}
}).catch(error=>{
console.log(error);
})
}
}
function toManage(id){
history.push(`/forums/manage/${section && section.id}`);
}
return (
<WhiteBack style={{ marginBottom: "15px", padding: "20px 30px" }}>
<FlexStart>
<Img src={section && section.picture ? section.picture : Radius} />
<div className="flex1">
<FlexAJ>
<P style={{ marginBottom: "0px" }}>{section && section.title}</P>
<AlignCenter>
{
operation ?
<Button onClick={()=>toManage(section && section.id)}>
<i className="iconfont icon-shezhi2"></i>板块管理
</Button>
:""
}
<Button onClick={()=>saveForum(section && section.id)} style={{ marginLeft: "30px" }}>
<i className={watched?"iconfont icon-pingfen-xian":"iconfont icon-pingfen-xian"}></i>{watched?"取消收藏":"收藏"}
</Button>
</AlignCenter>
</FlexAJ>
{section && section.description ? (
<Prag>{section.description}</Prag>
) : (
""
)}
<FlexAJ className="mt10">
<span>
<Spanleft>版主<Link className="grey-9" to={`/accounts/${sectionUser && sectionUser.user_login}`}>{sectionUser && sectionUser.username}</Link></Spanleft>
{forumModers && forumModers.length > 0 ? (
<Spanleft>
管理员
{forumModers.map((item, key) => {
return key < forumModers.length - 1
? <span><Link className="grey-9" to={`/accounts/${item.user_login}`}>{item.username}</Link></span>
: <Link className="grey-9" to={`/accounts/${item.user_login}`}>{item.username}</Link>;
})}
</Spanleft>
) : (
""
)}
</span>
<span>
<Spanright>
版块主题<label>{section && section.memos_count}</label>
</Spanright>
{section && section.publish_today_coun ? (
<Spanright>
今日发帖<label>{section.publish_today_count}</label>
</Spanright>
) : (
""
)}
{section && section.replies_today_count ? (
<Spanright>
今日回帖<label>{section.replies_today_count}</label>
</Spanright>
) : (
""
)}
</span>
</FlexAJ>
</div>
</FlexStart>
</WhiteBack>
);
};

View File

@ -0,0 +1,42 @@
import React , { useState , useEffect } from "react";
import { Link } from 'react-router-dom';
export default ({user}) => {
return (
<ul className="urllist">
{
user && user.login ?
<React.Fragment>
<Link to={`/accounts/${user.login}/memos`}>
<span>我的帖子</span>
<i className="iconfont icon-youjiantou"></i>
</Link>
<Link to={`/accounts/${user && user.login}/stars`}>
<span>我的收藏</span>
<i className="iconfont icon-youjiantou"></i>
</Link>
<Link to={`/accounts/${user && user.login}/interesting`}>
<span>我感兴趣的论坛</span>
<i className="iconfont icon-youjiantou"></i>
</Link>
</React.Fragment>
:
<React.Fragment>
<a href={"/login"}>
<span>我的帖子</span>
<i className="iconfont icon-youjiantou"></i>
</a>
<a href={`/login`}>
<span>我的收藏</span>
<i className="iconfont icon-youjiantou"></i>
</a>
<a href={`/login`}>
<span>我感兴趣的论坛</span>
<i className="iconfont icon-youjiantou"></i>
</a>
</React.Fragment>
}
</ul>
);
};

View File

@ -0,0 +1,24 @@
import React from 'react';
import { AlignCenter } from '../css/layout';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
const Img = styled.img`{
width:20px;
height:20px;
border-radius:50%;
margin-right:10px;
}`
const Span = styled.span`{
color:#999;
}`
export default (({name,url,login})=>{
return(
<Link to={`/accounts/${login}`}>
<AlignCenter>
<Img src={url}/>
<Span>{name}</Span>
</AlignCenter>
</Link>
)
})

View File

@ -0,0 +1,29 @@
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
export default ({ url , name , column , login })=>{
const Img = styled.span`
display:flex;
${column && "flex-direction: column;text-align:center;"}
align-items: center;
& img{
width:30px;
height:30px;
border-radius:50%;
}
${!column && `
& span{
margin-left:8px;
}`
}
`;
return(
<Link to={`/accounts/${login}`}>
<Img>
<img src={url} alt=""/>
<span className="task-hide" style={{maxWidth:"84px",textAlign:"center"}}>{name}</span>
</Img>
</Link>
)
}

197
src/forums/Forums.js Normal file
View File

@ -0,0 +1,197 @@
import React, { useEffect, useState } from "react";
import { Pagination, Menu, Spin } from "antd";
import {
Box,
Long,
Short,
Gap,
WhiteBack,
LeftLine,
AlignCenter,
} from "./css/layout";
import Title from "./Component/Title";
import ListItem from "./Component/ListItem";
import "./css/All.scss";
import ListSearch from "./Component/ListSearch";
import UrlItem from "./Component/UrlItem";
import BestItem from "./Component/BestItem";
import DoubleItem from "./Component/DoubleItem";
import "./css/Theme.scss";
import { Link } from "react-router-dom";
import axios from "axios";
const PAGESIZE = 10; // 首页帖子每页只展示5条
function aa(props) {
const current_user = props.current_user;
const [data, setData] = useState(undefined); //接口所有数据
const [memos, setMemos] = useState([]); //帖子列表
const [sort, setSort] = useState("published_at"); //最新最热
const [listSpin, setListSpin] = useState(true);
const [memosCount, setMemosCount] = useState([]); //帖子数量
const [page, setPage] = useState(1); //分页
const [search, setSearch] = useState(undefined); //搜索内容
const [selectKey, setSelectKey] = useState(["hot"]);
const [hotMemos, setHotMemos] = useState([]); //热门推荐、版主推荐
const [forumSections, setForumSections] = useState([]); //二级模块
const [plate, setPlate] = useState(undefined); // 所有版块
useEffect(() => {
init();
}, [page, search, sort]);
async function init() {
setListSpin(true);
let url = `/memos.json`;
axios
.get(url, {
params: { page, search, sort , limit:PAGESIZE },
})
.then((result) => {
if (result) {
setData(result.data);
setMemos(result.data.memos);
setListSpin(false);
setMemosCount(result.data.memos_count);
setHotMemos(result.data.hottest_memos);
setForumSections(result.data.forum_sections);
}
})
.catch((e) => {
console.log(e);
});
}
useEffect(() => {
async function init() {
let url = `/forum_sections.json`;
axios
.get(url, {
params: { is_detail: true },
})
.then((result) => {
if (result) {
setPlate(result.data.forum_sections);
}
})
.catch((e) => {
console.log(e);
});
}
init();
}, []);
// 翻页
function changePage(page) {
setPage(page);
}
// 搜索
function onSearch(e) {
setSearch(e);
}
function changeMenu(e) {
setSelectKey(e.key);
if (e.key === "hot") {
setHotMemos(data.hottest_memos);
} else {
setHotMemos(data.recommend_memos);
}
}
function changeSort(value) {
setSort(value);
}
return (
<div className="clearfix educontent pt20">
<Box>
<Long>
<WhiteBack>
<Title>
<span>论坛首页</span>
<AlignCenter>
<span
onClick={() => changeSort("published_at")}
className={
sort === "published_at" ? "green cPointer" : "cPointer"
}
>
最新
</span>
<LeftLine
onClick={() => changeSort("replies_count")}
className={
sort === "published_at" ? "cPointer" : "green cPointer"
}
style={{ fontSize: "16px" }}
>
最热
</LeftLine>
</AlignCenter>
</Title>
<Spin spinning={listSpin}>
<div style={{ minHeight: "868px" }}>
<ListItem
memos={memos}
current_user={current_user}
calbackFunc={init}
confirm={props.confirm}
/>
</div>
</Spin>
<div className="center" style={{ padding: "25px 0px" }}>
<Pagination
showQuickJumper
current={page}
onChange={changePage}
total={memosCount}
pageSize={PAGESIZE}
/>
</div>
</WhiteBack>
</Long>
<Short>
<Gap>
<WhiteBack style={{ marginBottom: "15px" }}>
<div style={{ padding: "20px" }}>
<ListSearch onSearch={onSearch} current_user={current_user} showLoginDialog={props && props.showLoginDialog}/>
</div>
<div style={{ padding: "0px 20px", borderTop: "1px solid #eee" }}>
<UrlItem user={current_user} />
</div>
</WhiteBack>
<WhiteBack style={{ marginBottom: "15px" }}>
<Title>热门话题</Title>
<BestItem memos={data && data.hottest_memos} selectKey={selectKey} />
</WhiteBack>
<WhiteBack>
<Title>版主推荐</Title>
<BestItem memos={data && data.recommend_memos} selectKey={selectKey} />
</WhiteBack>
</Gap>
</Short>
</Box>
{plate && plate.length > 0
? plate.map((item, key) => {
return item.children_tags && item.children_tags.length > 0 ? (
<WhiteBack style={{ marginBottom: "15px" }}>
{/* <Title className="fwt-600">{item.name}</Title> */}
<Title className="fwt-600">
<Link to={`/forums/theme/${item.id}`}>
{item.name}
</Link>
</Title>
<DoubleItem plate={item.children_tags} {...props} />
</WhiteBack>
) : (
""
);
})
: ""}
</div>
);
}
export default aa;

250
src/forums/ForumsDetail.jsx Normal file
View File

@ -0,0 +1,250 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
import { getUrl } from "educoder";
import { Breadcrumb, Spin, Empty } from "antd";
import { Link } from "react-router-dom";
import {
WhiteBack,
Box,
Long,
Short,
Gap,
FlexAJ,
AlignCenter,
P,
} from "./css/layout";
import Title from "./Component/Title";
import RenderHtml from "../components/render-html";
import { Type, Tag } from "./Component/ItemType";
import Drop from "./Component/ItemDropDown";
import UserInfo from "./Component/UserInfo";
import Prise from "./Component/Prise";
import ListItem from "./Component/ListItem";
import AuthorCard from "./Component/AuthorCard";
import BestItem from "./Component/BestItem";
import Original from "./image/original.png";
import Comments from "./new_comments/comments";
import Attachments from '../forge/Upload/attachment';
function memo_show(props) {
const memo_id = props.match.params.id;
const current_user = props.current_user;
const [memo, getMemo] = useState(null);
const [memoUser, getMemoUser] = useState(null);
const [replies, setReplies] = useState([]);
const [memoImage, setMemoImage] = useState(null);
const [recent_memos, setRecentmemos] = useState([]);
const [bannedPermission, setPermission] = useState(undefined);
const [isBanned, setIsBanned] = useState(false);
const [page, setListPage] = useState(1);
const [limit, setLimitType] = useState(5);
const [isSpin, setSpinType] = useState(false);
const [dropPermission, setDropPermission] = useState({});
useEffect(() => {
init();
related_memos();
}, [memo_id]);
async function init() {
setSpinType(true);
let url = `/memos/${memo_id}.json`;
axios
.get(url)
.then((result) => {
if (result) {
let per = {
admin:result.data && result.data.is_current_admin,
login: result.data && result.data.current_login,
banned_permission:result.data.banned_permission,
is_currentUser: result.data.author_info ? result.data.author_info.is_current_user : undefined,
is_fine: result.data.memo && result.data.memo.is_fine,
sticky: result.data.memo && result.data.memo.sticky,
memo_watched: result.data.memo && result.data.memo.memo_watched,
is_deleted:result.data.memo && result.data.memo.apply_destroy
}
setPermission(per);
getMemo(result.data.memo);
setMemoImage(result.data.memo_image_info); //
setRecentmemos(result.data.recent_memos);
setIsBanned(result.data.is_banned);
getMemoUser(result.data.author_info);
}
setSpinType(false);
})
.catch((e) => {
setSpinType(false);
console.log(e);
});
}
function related_memos() {
let url = `/memos/${memo_id}/related_memos.json`;
axios
.get(url)
.then((result) => {
if (result) {
setReplies(result.data.memos);
}
setSpinType(false);
})
.catch((e) => {
setSpinType(false);
console.log(e);
});
}
return (
<div className="clearfix educontent pt20 minH400">
<Spin spinning={isSpin}>
{memo && memoUser ? (
<div>
<Breadcrumb separator=">" style={{ marginBottom: "10px" }}>
<Breadcrumb.Item>
<Link to={`/forums`}>论坛</Link>
</Breadcrumb.Item>
{memo && memo.forum_tag && memo.forum_tag.id && (
<Breadcrumb.Item>
<Link to={`/forums/theme/${memo && memo.forum_tag.id}`}>
{memo && memo.forum_tag.title}
</Link>
</Breadcrumb.Item>
)}
<Breadcrumb.Item>
{memo ? memo.subject : "帖子详情"}
</Breadcrumb.Item>
</Breadcrumb>
<Box>
<Long>
<WhiteBack>
<div style={{ padding: "0px 30px" }}>
<Title className="headerInfo">
<div className="headerInfoLeft">
{memo && Type(`${memo.tag_name}`)}
<p className="font-18 grey-3" style={{ marginBottom: "0px",maxWidth:"514px"}}>
{memo && memo.subject}
</p>
{memo && memo.is_fine && Tag("精华")}
{memo && memo.sticky && Tag("置顶")}
</div>
<div style={{width:"130px",textAlign:"right"}}>
{current_user && current_user.login ? (
<Drop permission={bannedPermission} id={memo_id} calbackFunc={init} confirm={props.confirm}/>
):""}
</div>
{memo && memo.is_original && (
<img
src={Original}
className="originalTag"
width="80px"
/>
)}
</Title>
<AlignCenter className="font-12 pt15 pb15">
<UserInfo
url={getUrl(memoUser.image_url)}
name={memoUser.username}
login={memoUser.login}
/>
<span className="icon-wrap ml20">
<i className="iconfont icon-zhengyan font-18"></i>
<span className="span-text">
{memo && memo.viewed_count}
</span>
</span>
<span className="icon-wrap ml20">
<i className="iconfont icon-pinglun1 font-14"></i>
<span className="span-text">
{memo && memo.replies_count}
</span>
</span>
<span className="grey-8 ml20">
{memo && memo.published_time}
</span>
{memo && memo.apply_destroy && (
<span className="orange ml20">已申请删帖</span>
)}
</AlignCenter>
<div className="bor-bottom-greyE pb20 mb15">
{memoImage && (
<div className="pb20">
<img src={memoImage.url} style={{width: "100%"}}></img>
</div>
)}
<RenderHtml
className="tipsContent"
value={memo.content}
/>
{memo.attachment_url && memo.attachment_url.length > 0 &&
<Attachments
attachments={memo.attachment_url}
canDelete={false}
/>}
</div>
{memo.reprint_link && (
<p className="font-12 grey-8">转载自{memo.reprint_link}</p>
)}
<Prise
isPrised={memo.user_praise}
num={memo.praises_count}
memo_id={memo.id}
container_type="Memo"
current_login={memoUser && memoUser.current_login}
props={props}
/>
</div>
</WhiteBack>
<WhiteBack className="mt15">
<Comments
target_id={memo && memo.id}
target_type="memos"
current_user_image={memoUser && memoUser.current_image_url}
current_login={memoUser && memoUser.current_login}
props={props}
/>
</WhiteBack>
{
replies && replies.length > 0 &&
<WhiteBack className="mt15">
<Title>
<span className="greenLiftLine">相关推荐</span>
</Title>
<ListItem memos={replies} calbackFunc={related_memos} current_user={current_user} confirm = {props.confirm}/>
</WhiteBack>
}
</Long>
<Short>
<Gap>
<AuthorCard user={memoUser} />
<WhiteBack className="mt15">
<Title>
<span>作者最近文章</span>
<a
className="font-12 grey-9"
href={`/accounts/${memoUser && memoUser.login}/memos`}
>
更多
<i className="font-12 iconfont icon-youjiantou ml5"></i>
</a>
</Title>
<div className="memo-detail-ul">
<BestItem memos={recent_memos} selectKey={["recommend"]} />
</div>
</WhiteBack>
</Gap>
</Short>
</Box>
</div>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} className="pd200"></Empty>
)}
</Spin>
</div>
);
}
export default memo_show;

73
src/forums/Index.jsx Normal file
View File

@ -0,0 +1,73 @@
import React, { Component } from "react";
import { TPMIndexHOC } from "../modules/tpm/TPMIndexHOC";
import { SnackbarHOC } from "educoder";
import { CNotificationHOC } from "../modules/courses/common/CNotificationHOC";
import { Route, Switch, Redirect } from "react-router-dom";
import Loading from "../Loading";
import Loadable from "react-loadable";
import "./css/Index.css";
import "./css/All.scss";
const Detail = Loadable({
loader: () => import("./ForumsDetail"),
loading: Loading,
});
const Theme = Loadable({
loader: () => import("./Theme"),
loading: Loading,
});
const Forums = Loadable({
loader: () => import("./Forums"),
loading: Loading,
});
const New = Loadable({
loader: () => import("./New"),
loading: Loading,
});
class Index extends Component {
componentDidUpdate=()=>{
this.props.history.listen(()=>{
if (document.body.scrollTop || document.documentElement.scrollTop > 0) {
window.scrollTo(0, 0)
}
})
}
render() {
return (
<div className="newMain">
<Switch {...this.props}>
{/* 主题全部 */}
<Route
path="/forums/theme/:plateMainId"
render={(props) => (
<Theme {...this.props} {...props} />
)}
></Route>
{/* 详情 */}
<Route
path="/forums/:id/detail"
render={(props) => <Detail {...this.props} {...props} />}
></Route>
<Route
path="/forums/:forumId/edit"
render={(props) => <New {...this.props} {...props} />}
></Route>
<Route
path="/forums/new"
render={(props) => <New {...this.props} {...props} />}
></Route>
<Route path="/forums" render={(props) => <Forums {...this.props} {...props}/>}></Route>
<Redirect from="/" to="/forums"/>
{/* <Route
exact
path="/"
render={(props) => (
<Forums {...this.props} {...props}/>
)}
></Route> */}
</Switch>
</div>
);
}
}
export default CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(Index)));

372
src/forums/New.jsx Normal file
View File

@ -0,0 +1,372 @@
import React, { useCallback, useState, useEffect, forwardRef } from "react";
import { Banner, WhiteBack, AlignCenter, Greenback } from "./css/layout";
import { Form, Input, Select, Radio, Button, notification, Spin } from "antd";
import MDEditor from "../modules/tpm/challengesnew/tpm-md-editor";
import Uplaod from "../forge/Upload/Index";
import UplaodCover from "../forge/Upload/Cover";
import Attachments from '../forge/Upload/attachment';
import "./css/All.scss";
import axios from "axios";
const Option = Select.Option;
// forum_id: values.forum_id, //id, int
// attachment_id: ImgInfo.attachment_id, //id
// children_forum_id: values.children_forum_id, //idint
// attachments, //id
// memo: {
// //
// subject: values.subject, //
// content, //
// tag_id: values.tag_id, //12
// is_original: values.is_original, //true
// reprint_link: values.reprint_link, //is_originalfalse
// }
function New(props, ref) {
const {
form: { getFieldDecorator, validateFields, setFieldsValue },
} = props;
const [parentPlate, setParentPlate] = useState(undefined); //
const [childPlate, setChildPlate] = useState(undefined); //
const [content, setContent] = useState(""); //
const [ImgInfo, setImgInfo] = useState(undefined); //
const [attachments, setAttachments] = useState(undefined); //id
const [spinning, setSpining] = useState(false);
const [ fileList ,setFileList ] = useState(undefined);
const [ originalType , setOriginalType ] = useState(2);
let forumId = props.match.params.forumId;
useEffect(() => {
setFieldsValue({
tag_id: 1,
is_original: 2,
});
getPlate();
}, []);
function getDetail(plate){
if(forumId){
const url = `/memos/${forumId}/edit.json`;
axios.get(url).then(result=>{
if(result){
setContent(result.data.content);
let forum_id = result.data.forum_section && result.data.forum_section.forum_id;
let child = plate && plate.filter((item) => item.id === forum_id);
let memo_original = result.data.is_original ? 1 : 2
setChildPlate(child && child.length>0 && child[0].children_tags);
setOriginalType(memo_original)
setFieldsValue({
...result.data,
forum_id,
tag_id: result.data.tag_id,
children_forum_id: result.data.children_forum_section && result.data.children_forum_section.children_forum_id ? result.data.forum_section_id : undefined,
is_original: memo_original
});
let attachments_url = result.data.attachments_url && result.data.attachments_url.map((item,key)=>{
return({
title:item.title,
...item
})
});
let array = result.data.attachments_url && result.data.attachments_url.map((item,key)=>{
return item.id
})
setAttachments(array);
setFileList(attachments_url);
setImgInfo(result.data.memo_image_info);
}
}).catch(error=>{
console.log(error);
})
}
}
//
function getPlate() {
setSpining(true);
const url = `/forum_sections.json`;
axios.get(url)
.then((result) => {
if (result && result.data) {
setParentPlate(result.data.forum_sections);
setFieldsValue({
forum_id: 0,
});
if(forumId){
getDetail(result.data.forum_sections);
}
setSpining(false);
}
})
.catch((error) => {
console.log(error);
});
}
//
function changeParent(e) {
let child = parentPlate.filter((item) => item.id === e);
if (child && child.length === 1) {
setChildPlate(child[0].children_tags);
setFieldsValue({
children_forum_id:
child[0].children_tags && child[0].children_tags.length > 0
? child[0].children_tags[0].id
: "",
});
} else {
setChildPlate(undefined);
setFieldsValue({
children_forum_id: "",
});
}
}
const helper = useCallback(
(label, name, rules, widget, isRequired) => (
<React.Fragment>
<span className={isRequired ? "lables must" : "lables"}>{label}</span>
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
</React.Fragment>
),
[]
);
//
function onContentChange(e) {
setContent(e);
}
//
function UploadFunc(e) {
attachments && attachments.length>0 && attachments.map((item,key)=>{
return e.push(item && item.id)
});
setAttachments(e);
}
//
function getImageUrl(imgId) {
setImgInfo(imgId);
}
//
function returnNew() {
props.history.goBack();
}
//
function sureNew() {
validateFields((error, values) => {
if (!error) {
setSpining(true);
let params = {
forum_id: values.forum_id,
attachment_id: ImgInfo && ImgInfo.id,
children_forum_id: values.children_forum_id,
attachments,
memo: {
subject: values.subject,
content,
tag_id: values.tag_id,
is_original: values.is_original,
reprint_link: values.reprint_link,
}
}
if(forumId){
//
const url = `/memos/${forumId}.json`;
axios.put(url, params).then((result) => {
if (result && result.data) {
notification.open({
message: "提示",
description: result.data.message,
});
if(result.data.status === 1){
props.history.push(`/forums/${forumId}/detail`);
}
setSpining(false);
}
})
.catch((error) => {
setSpining(true);
console.log(error);
});
}else{
//
const url = `/memos.json`;
console.log("params", params)
axios.post(url, params).then((result) => {
if (result && result.data) {
notification.open({
message: "提示",
description: result.data.message,
});
if(result.data.status === 1){
props.history.push(`/forums/${result.data.memo_id}/detail`);
}
setSpining(false);
}
})
.catch((error) => {
setSpining(false);
console.log(error);
});
}
}
});
}
function showNotification(description){
notification.open({
message:"提示",
description
})
}
function changeOriginal(e){
setOriginalType(e.target.value);
}
//
function deleteFunc(id){
let team = attachments && attachments.length>0 && attachments.filter(item=>item!==id);
setAttachments(team);
}
return (
<div className="educontent pt20 pb30">
<WhiteBack>
<Banner>{ forumId ? "编辑" : "新建"}</Banner>
<Spin spinning={spinning}>
<Form className="formStyle">
{helper(
"标题",
"subject",
[{ required: true, message: "请输入标题" }],
<Input placeholder="请输入标题" maxLength={50} />,
true
)}
{helper(
"内容",
"content",
[{ required: true, message: "请输入内容" }],
<MDEditor
placeholder={"请输入描述信息"}
watch={true}
height={250}
mdID={"oj-description"}
initValue={content}
onChange={onContentChange}
></MDEditor>,
true
)}
<div className="mb20">
<Uplaod
className="UploadStyle"
isComplete={true}
load={UploadFunc}
content={
<div>
<span className="green">上传附件</span>
<span className="grey-9">单个文件50M以内</span>
</div>
}
size={100}
></Uplaod>
{forumId && fileList && fileList.length > 0 ? (
<Attachments
attachments={fileList}
showNotification={showNotification}
canDelete={true}
deleteFunc={deleteFunc}
/>
) : (
""
)}
</div>
<p>
<span className="lables must">主题板块</span>
</p>
<div className="inlineEnd">
{helper(
"",
"forum_id",
[{ required: true, message: "请选择主题板块" }],
<Select onChange={changeParent}>
<Option value={0}>请选择主题板块</Option>
{parentPlate &&
parentPlate.length > 0 &&
parentPlate.map((item, key) => {
return <Option value={item.id}>{item.name}</Option>;
})}
</Select>
)}
{helper(
"",
"children_forum_id",
[],
<Select>
{childPlate &&
childPlate.length > 0 &&
childPlate.map((item, key) => {
return <Option value={item.id}>{item.title}</Option>;
})}
</Select>
)}
</div>
<AlignCenter className="clearfix mt20 mb10">
<span className="lables fl">上传封面</span>
<span className="grey-9">
(只支持JPGPNGJPEG大小不超过2M建议尺寸4:3)
</span>
</AlignCenter>
<UplaodCover
imageUrl={ImgInfo && ImgInfo.url}
getImageUrl={getImageUrl}
/>
{helper(
"帖子标签",
"tag_id",
[{ required: true, message: "请选择帖子标签" }],
<Select style={{ width: "260px" }}>
<Option value={1}>交流</Option>
<Option value={2}>求助</Option>
</Select>,
true
)}
{helper(
"是否原创",
"is_original",
[],
<Radio.Group onChange={changeOriginal}>
<Radio value={1}></Radio>
<Radio value={2}></Radio>
</Radio.Group>,
true
)}
{ originalType !==1? helper(
"转载自",
"reprint_link",
[],
<Input style={{ width: "260px" }} placeholder="转载链接"/>
) :""}
</Form>
</Spin>
</WhiteBack>
<div className="mt20">
<Button onClick={returnNew}>返回</Button>
<Greenback onClick={sureNew} className="ml20">
{ forumId ? "保存" : "提交"}
</Greenback>
</div>
</div>
);
}
export default Form.create()(forwardRef(New));

18
src/forums/Nodata.js Normal file
View File

@ -0,0 +1,18 @@
import React , { Component } from 'react';
import nodata from './image/nodata.png';
class Nodata extends Component{
render(){
const { _html } = this.props;
return(
<div className="none_panels">
<div>
<img src={nodata} alt="" />
<div className="none_p_title">{_html}</div>
</div>
</div>
)
}
}
export default Nodata;

157
src/forums/Theme.js Normal file
View File

@ -0,0 +1,157 @@
//主题页
import React, { useState, useEffect } from "react";
import { Breadcrumb, Menu, Spin } from "antd";
import { Link } from "react-router-dom";
import ListSearch from './Component/ListSearch';
import { Box, Long, Short, Gap, WhiteBack, LeftLine, FlexAJ, AlignCenter } from './css/layout';
import axios from 'axios';
import Tops from './Component/Top';
import ListItem from './Component/ListItem';
import Paginations from './Component/Paginations';
import ThemeRight from './ThemeRight';
import Nodata from './Nodata';
import './css/Theme.scss';
function theme(props) {
const [ operation , setOperation ]= useState(undefined);
const [menuKey, setMenuKey] = useState("all");//tab
const [search, setSearch] = useState(undefined);//搜索内容
const [listSpin, setListSpin] = useState(true);
const [sort, setSort] = useState("published_at");//排序
const [pageSize, setPageSize] = useState(0);//每页条数
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [memos, setMemos] = useState(undefined);//帖子列表
const [ breadCrumb , setBreadCrumb ] = useState(undefined);
const [ headData , setHeadData] = useState(undefined);// 头部信息
let plateMainId = props.match.params.plateMainId;
let current_user = props.current_user;
// console.log("111",props);
// 获取列表
useEffect(() => {
if (plateMainId) {
InitList();
}
}, [plateMainId, page, search, menuKey, sort])
async function InitList() {
setListSpin(true);
const url = `/memos/forum_memos/${plateMainId}.json`;
axios.get(url, {
params: {
page, search, sort, select_type: menuKey
}
}).then(result => {
if (result) {
setMemos(result.data.memos);
setTotal(result.data.memos_count);
setListSpin(false);
setPageSize(result.data.limit);
}
}).catch(error => {
console.log(error);
})
}
useEffect(()=>{
async function getTopInfo(){
const url = `/memos/forum_memos_head/${plateMainId}.json`;
axios.get(url).then(result=>{
if(result && result.data){
setBreadCrumb(result.data.bread_crumb.forum_tag);
setHeadData(result.data);
filterUsers(current_user,result.data.forum_moders,result.data.forum_section_user);
}
}).catch(error=>{
console.log(error);
})
}
if(plateMainId && current_user) getTopInfo();
},[plateMainId, current_user])
let pathname = props.location.pathname;
useEffect(() => {
if (pathname) {
if (pathname === "/forums/involvedTopics") {
setMenuKey("my_topics");
} else if (pathname === "/forums/topic") {
setMenuKey("my_memos");
} else if (pathname === "/forums/recommend") {
setMenuKey("is_fine");
} else {
setMenuKey("all");
}
}
}, [pathname])
// 判断当前用户是否是版主或者管理员
function filterUsers(currentUser,moders,users){
if(currentUser){
let admin = moders && moders.filter(item=>item.user_login === current_user.login);//管理员
let creater = users && users.user_login === current_user.login;//版主
setOperation((admin && admin.length>0)|| creater);
}
}
// 搜索
function onSearch(e) {
setSearch(e);
}
// 翻页
function changePage(page) {
setPage(page);
}
function changeMenu(e) {
setMenuKey(e.key);
}
return (
<div className="clearfix educontent pt20" >
<Breadcrumb separator=">" style={{ marginBottom: "10px" }}>
<Breadcrumb.Item><Link to={`/forums`}>论坛交流</Link></Breadcrumb.Item>
{ breadCrumb && breadCrumb.title ? <Breadcrumb.Item><Link to={`/forums/theme/${breadCrumb.id}`}>{breadCrumb.title}</Link></Breadcrumb.Item>:"" }
{ breadCrumb && breadCrumb.children_bread_crumb ?<Breadcrumb.Item>{breadCrumb.children_bread_crumb && breadCrumb.children_bread_crumb.title}</Breadcrumb.Item>:""}
</Breadcrumb>
<Tops operation={operation} plateMainId={plateMainId} headData = {headData} title={breadCrumb && breadCrumb.title} history={props.history}/>
<Box>
<Long>
<WhiteBack>
<FlexAJ style={{ borderBottom: "1px solid #eee" }}>
<Menu className="unlow newMenu" selectedKeys={[menuKey]} onClick={changeMenu} mode="horizontal">
<Menu.Item key="all">全部</Menu.Item>
<Menu.Item key="is_fine">推荐精华</Menu.Item>
<Menu.Item key="my_memos">我的话题</Menu.Item>
<Menu.Item key="my_topics">我参与的话题</Menu.Item>
</Menu>
<AlignCenter style={{ marginRight: "30px" }}>
<span onClick={() => setSort("published_at")} className={sort === "published_at" ? "green cPointer" : "cPointer"}>最新</span>
<LeftLine onClick={() => setSort("replies_count")} className={sort === "published_at" ? "cPointer" : "green cPointer"} style={{ fontSize: '14px' }}>最热</LeftLine>
</AlignCenter>
</FlexAJ>
<Spin spinning={listSpin}>
{
memos && memos.length > 0 ?
<div style={{ minHeight: "400px" }}>
<ListItem memos={memos} current_user={current_user} calbackFunc={InitList} confirm = {props.confirm}/>
</div>
:
<AlignCenter style={{height:"400px"}} className="bigNoData">
<Nodata _html={"暂无帖子~"} />
</AlignCenter>
}
</Spin>
<Paginations page={page} total={total} pageSize={pageSize} changePage={changePage} />
</WhiteBack>
</Long>
<Short>
<Gap>
<WhiteBack style={{ padding: "20px" }}>
<ListSearch onSearch={onSearch} current_user={current_user} showLoginDialog={props && props.showLoginDialog}/>
</WhiteBack>
<ThemeRight operation={operation} plateId={plateMainId}/>
</Gap>
</Short>
</Box>
</div>
)
} export default theme

52
src/forums/ThemeRight.jsx Normal file
View File

@ -0,0 +1,52 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
import AuthorItem from './Component/AuthorItem';
import BestModalItem from './Component/BestModalItem';
import Announcement from './Component/Announcement';
export default (({ plateId , operation })=> {
const [content, setContent] = useState(undefined);//
const [ recommand , setRecommand ]= useState(undefined);//
const [ author , setAuthor ]= useState(undefined);//
useEffect(()=>{
async function Init(){
const url = `/memos/forum_memos_right/${plateId}.json`;
axios.get(url).then(result=>{
if(result){
setContent({notice:result.data.notice,login:result.data.user_login,name:result.data.username});
setRecommand(result.data.recommend_forum_sections);
setAuthor(result.data.active_users);
}
}).catch(error=>{
console.log(error);
})
}
if(plateId){
Init();
}
},[plateId])
return (
<React.Fragment>
{
content &&
<div style={{ marginTop: "20px" }}>
<Announcement plateId={plateId} content={content} operation={operation}/>
</div>
}
{
author && author.length > 0 &&
<div style={{ marginTop: "20px" }}>
<AuthorItem author={author}/>
</div>
}
{
recommand && recommand.length > 0 &&
<div style={{ marginTop: "20px" }}>
<BestModalItem recommand={recommand}/>
</div>
}
</React.Fragment>
);
});

298
src/forums/css/All.scss Normal file
View File

@ -0,0 +1,298 @@
// page:UrlItem
.urllist {
& > a {
display: flex;
justify-content: space-between;
padding: 12px 0px;
color: #333;
align-items: center;
border-bottom: 1px solid #eee;
i {
font-size: 14px;
color: #ccc;
}
}
& > a:last-child {
border-bottom: none;
}
}
.memo-detail-ul{
.BestUl{height: auto !important;}
}
.BestUl {
padding: 15px 25px;
min-height: 288px;
li {
padding: 6px 0px 5px 13px;
position: relative;
&::before {
position: absolute;
width: 4px;
height: 4px;
content: "";
border-radius: 50%;
background-color: #21b350;
left: 0px;
top: 19px;
}
}
}
.doubleItems {
padding: 0px 30px;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
& > a {
padding: 20px 0px;
width: 50%;
box-sizing: border-box;
border-bottom: 1px solid #eee;
display: flex;
align-items: flex-start;
text-align: justify;
cursor: pointer;
&:nth-child(odd) {
padding-right: 30px;
}
&:nth-child(even) {
padding-left: 30px;
}
&:last-child {
border-bottom: none;
}
}
}
.unlow {
border: none !important;
}
.authorUl {
display: grid;
grid-template-columns: repeat(3, 33.33%);
grid-template-rows: 65px;
padding: 20px 30px;
& > span >img{
width:40px;
height:40px;
}
& > span:nth-child(3n + 1),
& > span:first-child {
align-items: flex-start;
img{margin-left: 10px;}
}
& > span:nth-child(3n) {
align-items: flex-end;
img{margin-right: 10px;}
}
}
.BestModalUl {
padding: 10px 30px;
& > div {
padding: 10px 0px;
}
}
.annContent {
margin-bottom: 10px;
overflow: hidden;
height: 100px;
line-height: 20px;
position: relative;
.annWords {
margin-bottom: 0px;
word-break: break-all;
}
}
.annBtn {
position: absolute;
right: 27px;
top: 90px;
color: #888;
background-color: #fff;
}
.none_panels{
text-align: center;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding:20px 0px;
img{
margin-bottom: 15px;
width:60%;
}
.none_p_title{
font-size: 14px;
color: #999;
}
}
.formStyle{
padding:20px 30px!important;
.lables{
position: relative;
color:#333;
margin-bottom: 5px;
display: block;
font-size: 16px;
}
.lables.must::before{
content: "*";
color: #F73030;
font-size: 18px;
position: absolute;
left: -15px;
height: 100%;
}
}
.UploadStyle{
border:none!important;
text-align: left!important;
background-color: #fff!important;
.ant-upload-drag-container{
i{
font-size: 50px;
margin-bottom: 15px;
color: #999;
}
display: flex!important;
flex-direction: column;
align-items: center;
border:1px dashed #eee;
padding:15px 0px;
}
.ant-upload{
padding:0px!important;
}
}
.ant-upload-list-item-card-actions .anticon.anticon-download{
display:none!important;
}
.inlineEnd{
display: flex;
align-items: center;
.ant-row.ant-form-item{
margin-bottom: 0px;
width:260px;
margin-right: 20px;
}
}
.headerInfo{
padding:20px 0px!important;
position: relative;
align-items:flex-start!important;
.headerInfoLeft{
display:flex;
flex:1;
align-items:center;
width:0;
}
.tag{
margin-top:0px!important;
}
.originalTag{
position: absolute;
right: 50px;
top:20px;
z-index: 10;
}
.tag{
margin-top:3px;
}
}
.icon-wrap {
margin-left: 30px;
color: #ccc;
float: left;
line-height: 20px;
display: flex;
align-items: center;
.span-text{
margin-left: 3px;
}
}
.tag{
display: inline-block;
height: 18px;
line-height: 18px;
padding:0px 8px;
font-size: 12px;
color:#fff;
margin-left: 8px;
border-radius: 12px;
&.tagRed{
background-color: #FA4A4A;
}
&.tagBlue{
background-color: #50C7FF;
}
&.tagOrange{
background-color: #FA6400;
}
}
.greenLiftLine{
font-size: 16px;
color: #333;
height: 22px;
line-height: 22px;
margin-left: 14px;
position: relative;
&::before{
position: absolute;
left: -14px;
height: 100%;
width:6px;
content: "";
top:0px;
background-color: #21B350;
}
}
.priseBox{
display: flex;
flex-direction: column;
align-items: center;
padding:20px 0px;
span:first-child{
height: 70px;
width: 70px;
line-height: 70px;
border-radius: 50%;
background-color:#F3F7F5 ;
text-align: center;
cursor: pointer;
i{
font-size: 27px !important;
color: #21B350;
}
}
span:last-child{
margin-top: 10px;
color:#888888 ;
};
}
.authorCard{
padding:40px 35px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
img{
height: 60px!important;
width:60px!important;
margin-bottom: 10px;
}
img + span{
color: #333;
font-size: 16px;
}
}
.tip_tag{
font-size: 12px;
background:rgba(238,238,238,1);
border-radius:10px;
color: #888;
margin-left: 8px;
padding: 1px 8px;
}
.b-bottom-none{border-bottom: none !important;}
.edu-text-center{text-align: center;}
.pd510{padding: 5px 10px;}
.fwt-600{font-weight: 600;}

84
src/forums/css/Index.css Normal file
View File

@ -0,0 +1,84 @@
ul,ol,dl{
margin:0px;
}
p{
margin-bottom: 8px;
}
.ml4{
margin-left: 4px;
}
.ml8{
margin-left: 8px;
}
.mt8{
margin-top: 8px;
}
.grey-3{
color:#333!important;
}.grey-8{
color:#888!important;
}.grey-9{
color:#999!important;
}
a.grey-9:hover{
color:#5091ff!important
}
.green{
color: #21B350!important;
}
.blue{
color: #5091FF!important;
}
.orange{
color: #FA6400!important;
}
.flex1{
flex:1;
}
.cPointer{
cursor: pointer;
}
.bigNoData .none_panels>div{
width:30%;
}
.markdown-body p{
white-space: pre-wrap;
}
/*超过隐藏*/
.task-hide {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.task-hide2 {
overflow:-moz-hidden-unscrollable; white-space: nowrap; text-overflow:ellipsis;
}
.center{
text-align: center;
}
.greenbtn{
height:30px;
line-height:30px;
border-radius:2px;
background-color:#28BD6C;
color:#fff!important;
padding:0px 12px;
display:inline-block;
min-width:80px;
text-align:center;
}
.forumList{
padding:0px 30px;
}
.forumList > li{
border-bottom: 1px solid #eee;
padding:15px 0px;
}
.forumList > li:last-child{
border-bottom: none;
}
.pd200{padding: 200px 0;}
.minH400{
min-height: 400px;
}

25
src/forums/css/Theme.scss Normal file
View File

@ -0,0 +1,25 @@
.newMenu .ant-menu-item, .ant-menu-submenu-title{
margin: 2px 0px 2px 30px!important;
padding:0px!important;
font-size: 16px;
}
.newMenu.ant-menu-horizontal > .ant-menu-item:hover,
.newMenu.ant-menu-horizontal > .ant-menu-item-selected,
.newMenu.ant-menu-horizontal > .ant-menu-submenu:hover{
color: #21B350!important;
border-bottom:2px solid #fff!important;
}
.newMenu.ant-menu-horizontal > .ant-menu-item-selected{
color: #21B350!important;
position:relative;
}
.newMenu.ant-menu-horizontal > .ant-menu-item-selected:after{
position:absolute;
width:100%;
height:2px;
background:#28BD6C;
content:"";
left:0px;
bottom:-4px;
}

Some files were not shown because too many files have changed in this diff Show More