Compare commits

..

61 Commits

Author SHA1 Message Date
黄心宇 e414343db3 cont. 2023-09-28 16:06:12 +08:00
黄心宇 06374112d8 header排序 2023-09-28 16:04:57 +08:00
黄心宇 f45d31b489 论坛隐藏特色专区 2023-09-28 15:31:09 +08:00
黄心宇 0580fcb2c0 帖子详情页动态修改title与head 2023-09-21 14:38:00 +08:00
黄心宇 baed9df260 动态修改title 2023-09-21 14:05:27 +08:00
caishi db9bfa7576 build 2023-05-31 17:37:14 +08:00
caishi c8a08bbd7f md alert 2023-04-21 14:04:04 +08:00
caishi 82b979894b md files and xss 2023-04-18 15:44:01 +08:00
caishi 7b430d8257 md 2023-04-18 15:31:51 +08:00
caishi 0accc419de xss 2023-04-18 15:25:27 +08:00
caishi 7423036bb0 列表不可点赞、点击跳转评论 2023-03-13 14:54:31 +08:00
caishi 5e70812ef1 document title 2022-11-29 13:47:39 +08:00
“xxq250” 58b8fb4ca8 fixed 发贴原创标识修正 2022-09-09 18:20:26 +08:00
caishi b56f0b27c2 头像url 2022-04-18 17:26:14 +08:00
caishi ae1f4246e4 remove 2022-03-22 10:33:10 +08:00
caishi 75f26ac136 头像跳转到个人主页 2022-03-22 10:32:44 +08:00
caishi 765840f2c8 main_site_url 2022-03-17 16:03:38 +08:00
caishi 5b4c103ad5 找回密码+注册的跳转地址 2022-03-17 15:43:50 +08:00
caishi 0ce87362d5 头部logo不需要默认logo 2021-12-01 16:22:50 +08:00
xxq250 77730321ee Merge pull request '头部底部样式,下拉框内容等和gitlink保持一致' (#281) from caishi/forgeplus-react:dev_wenba into dev_wenba 2021-11-24 15:57:36 +08:00
caishi 703c876c18 头像链接地址,下拉框内容 2021-11-24 15:54:12 +08:00
caishi 471b8dff3b Merge branch 'dev_wenba' of https://git.trustie.net/caishi/forgeplus-react into dev_wenba
# Conflicts:
#	public/css/edu-purge.css
#	src/Nav/Footer.jsx
#	src/modules/tpm/TPMIndex.css
2021-11-24 10:28:10 +08:00
caishi 68501898bd 头部,底部样式和gitlink统一 2021-11-24 10:26:23 +08:00
caishi d96611893f 论坛:头部、底部样式 2021-10-29 18:38:54 +08:00
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
558 changed files with 34783 additions and 42883 deletions

View File

@ -1,16 +0,0 @@
{
"presets": [
"es2015",
"react",
"stage-2"
],
"plugins": [[
"transform-runtime",
{
"helpers": false,
"polyfill": false,
"regenerator": true,
"moduleName": "babel-runtime"
}
]]
}

1190
.idea/workspace.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
{
}

124
LICENSE
View File

@ -1,124 +0,0 @@
木兰宽松许可证, 第2版
2020年1月 http://license.coscl.org.cn/MulanPSL2
您对“软件”的复制、使用、修改及分发受木兰宽松许可证第2版“本许可证”的如下条款的约束
0. 定义
“软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
“贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
“法人实体” 是指提交贡献的机构及其“关联实体”。
“关联实体” 是指对“本许可证”下的行为方而言控制、受控制或与其共同受控制的机构此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
1. 授予版权许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
2. 授予专利许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
3. 无商标许可
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可但您为满足第4条规定的声明义务而必须使用除外。
4. 分发限制
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
5. 免责声明与责任限制
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
6. 语言
“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
条款结束
如何将木兰宽松许可证第2版应用到您的软件
如果您希望将木兰宽松许可证第2版应用到您的新软件为了方便接收者查阅建议您完成如下三步
1 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
2 请您在软件包的一级目录下创建以“LICENSE”为名的文件将整个许可证文本放入该文件中
3 请将如下声明文本放入每个源文件的头部注释中。
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
Mulan Permissive Software LicenseVersion 2
Mulan Permissive Software LicenseVersion 2 (Mulan PSL v2)
January 2020 http://license.coscl.org.cn/MulanPSL2
Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
0. Definition
Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
Contribution means the copyrightable work licensed by a particular Contributor under this License.
Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
Legal Entity means the entity making a Contribution and all its Affiliates.
Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, control means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
1. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
2. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
3. No Trademark License
No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4.
4. Distribution Restriction
You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
5. Disclaimer of Warranty and Limitation of Liability
THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW ITS CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
6. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS
How to Apply the Mulan Permissive Software LicenseVersion 2 (Mulan PSL v2) to Your Software
To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
Create a file named "LICENSE" which contains the whole context of this License in the first directory of your software package;
Attach the statement to the appropriate annotated syntax at the beginning of each source file.
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.

View File

@ -1,16 +0,0 @@
<h3>前端react环境安装</h3>
<p>1、 安装node v6.9.x此安装包含了node和npm。</p>
<p>2、 安装cnpm命令行 npm install -g cnpm --registry=https://registry.npm.taobao.org</p>
<p>3、 安装依赖的js库public/react目录下<即项目package.json所在目录>,开启命令行): cnpm install</p>
<p>4、 如果你的ruby服务使用的是3000端口则需要在package.json中修改"port"参数的值</p>
<p>5、 启动服务(命令行-目录同3 npm start</p>
<p>6、 build初始化 npm run build</p>
<h3>分支信息:</h3>
<p>相关代码提交到对应分支能上线的代码先提交到develop分支上测试版测试通过后合并提交到master分支上线正式版</p>
<p>master:开发环境(正式环境)</p>
<p>develop:测试环境</p>
<p>dev_local:本地版本</p>
<p>dev_chain:含有区块链相关内容的分支</p>
<p>PS:新增加的需求功能先新建新分支开发在测试版测试没问题后再分别合并到develop和master分支</p>

View File

@ -17,10 +17,18 @@ const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const paths = require("./paths");
const getClientEnvironment = require("./env");
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
let publicPath = "/react/build/";
// let nodeEnv = process.env.NODE_ENV
// if (nodeEnv === 'testBuild') {
// publicPath = 'https://testforgeplus.trustie.net/react/build/';
// }
// if (nodeEnv === 'production') {
// publicPath = 'https://forgeplus.trustie.net/react/build/';
// }
const publicUrl = publicPath.slice(0, -1);
// const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
const shouldUseSourceMap = process.env.NODE_ENV !== "production";
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
const env = getClientEnvironment(publicPath);
// This is the production configuration.
@ -55,8 +63,7 @@ module.exports = {
},
bail: true,
mode: "production",
// devtool: false, //测试版
devtool: shouldUseSourceMap?'source-map':false,
devtool: false, //测试版
entry: [require.resolve("./polyfills"), paths.appIndexJs],
output: {
path: paths.appBuild,
@ -134,6 +141,7 @@ module.exports = {
name: "static/media/[name].[hash:8].[ext]",
},
},
// Process JS with Babel.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
@ -153,8 +161,10 @@ module.exports = {
],
},
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,

13422
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,21 @@
{
"name": "forge",
"version": "3.0.0",
"name": "educoder",
"version": "0.1.0",
"private": true,
"dependencies": {
"@monaco-editor/react": "^2.3.0",
"@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",
"babel-core": "6.26.0",
"babel-eslint": "7.2.3",
"babel-jest": "20.0.3",
"babel-loader": "7.1.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-polyfill": "^6.26.0",
"babel-preset-react-app": "^3.1.1",
"babel-runtime": "6.26.0",
"bizcharts": "^3.5.8",
@ -22,17 +23,15 @@
"case-sensitive-paths-webpack-plugin": "2.1.1",
"chalk": "1.1.3",
"classnames": "^2.2.5",
"clipboard": "^2.0.8",
"clipboard": "^2.0.6",
"code-prettify": "^0.1.0",
"codemirror": "^5.53.0",
"connected-react-router": "4.4.1",
"cross-env": "^7.0.3",
"css-loader": "^3.5.2",
"dompurify": "^2.0.15",
"dompurify": "^3.0.2",
"dotenv": "4.0.0",
"dotenv-expand": "4.2.0",
"echarts": "^4.9.0",
"echarts-wordcloud": "^2.0.0",
"echarts": "^4.7.0",
"editor.md": "^1.5.0",
"eslint": "4.10.0",
"eslint-config-react-app": "^2.1.0",
@ -49,7 +48,6 @@
"install": "^0.12.2",
"jest": "20.0.4",
"js-base64": "^2.5.2",
"js2wordcloud": "^1.1.12",
"katex": "^0.11.1",
"lodash": "^4.17.15",
"loglevel": "^1.6.8",
@ -66,7 +64,7 @@
"postcss-loader": "2.0.8",
"promise": "8.0.1",
"prop-types": "^15.6.1",
"qrcode.react": "^1.0.1",
"qrcode.react": "^1.0.0",
"qs": "^6.9.3",
"quill": "^1.3.7",
"quill-delta-to-html": "^0.11.0",
@ -96,7 +94,6 @@
"react-resizable": "^1.10.1",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-slick": "^0.28.1",
"react-split-pane": "^0.1.91",
"react-url-query": "^1.5.0",
"react-zmage": "^0.8.5-beta.31",
@ -104,11 +101,9 @@
"redux-thunk": "2.3.0",
"rsuite": "^4.3.4",
"sass-loader": "7.3.1",
"save-dev": "0.0.1-security",
"scroll-into-view": "^1.14.2",
"showdown": "^1.9.1",
"showdown-katex": "^0.8.0",
"slick-carousel": "^1.8.1",
"store": "^2.0.12",
"style-loader": "0.19.0",
"styled-components": "^4.4.1",
@ -119,13 +114,12 @@
"webpack-manifest-plugin": "^2.2.0",
"whatwg-fetch": "2.0.3",
"wrap-md-editor": "^0.2.20",
"xterm": "4.8.1",
"xterm-addon-fit": "0.4.0"
"xss": "^1.0.14"
},
"scripts": {
"start": "node --max_old_space_size=15360 scripts/start.js",
"build": "cross-env NODE_ENV=production node --max_old_space_size=15360 scripts/build.js",
"test-build": "cross-env NODE_ENV=testBuild node --max_old_space_size=15360 scripts/build.js",
"build": "NODE_ENV=production node --max_old_space_size=15360 scripts/build.js",
"test-build": "NODE_ENV=testBuild node --max_old_space_size=15360 scripts/build.js",
"pre-build": "NODE_ENV=preBuild node --max_old_space_size=15360 scripts/build.js",
"gen_stats": "NODE_ENV=production webpack --profile --config=./config/webpack.config.prod.js --json > stats.json",
"ana": "webpack-bundle-analyzer ./stats.json",
@ -187,18 +181,11 @@
"eslintConfig": {
"extends": "react-app"
},
"proxy": "http://localhost:3001",
"proxy": "http://localhost:3000",
"port": "3007",
"devDependencies": {
"@babel/runtime": "7.0.0-beta.51",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-plugin-import": "^1.13.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"compression-webpack-plugin": "^1.1.12",
"concat": "^1.0.3",
"happypack": "^5.0.1",
@ -206,7 +193,6 @@
"node-sass": "^4.12.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"purgecss": "^2.1.2",
"react-json-view": "^1.21.3",
"reqwest": "^2.0.5",
"resize-observer-polyfill": "^1.5.1",
"terser-webpack-plugin": "^2.3.5",

File diff suppressed because one or more lines are too long

View File

@ -38,6 +38,78 @@
box-sizing: border-box;
}
.head-nav ul#header-nav li {
float: left;
height: 60px;
line-height: 60px;
margin-right: 30px;
cursor: pointer;
position: relative;
font-size: 16px
}
.head-nav ul#header-nav li a {
display: block;
height: 100%;
width: 100%;
color: #fff
}
.head-nav ul#header-nav li a:hover {
color: #cccccc;
}
.head-nav ul#header-nav li:last-child {
margin-right: 0px
}
.head-nav ul#header-nav li.active a {
color: #459be5 !important;
}
.head-nav ul#header-nav li.active p {
color: #459be5 !important;
}
.head-nav ul#header-nav li p:hover {
color: #cccccc;
}
.head-nav ul#header-nav li p {
display: block;
height: 100%;
width: 100%;
color: #fff
}
.head-nav ul#header-nav li.active div ul li a {
color: #000 !important;
}
.head-nav ul#header-nav li.active div ul li a:hover {
color: #FFF !important;
}
.head-nav ul#header-nav li.active ul li a {
color: #000 !important;
}
.head-nav ul#header-nav li.active ul li a:hover {
color: #FFF !important;
}
.head-nav ul#header-nav li.active:after {
content: '';
position: absolute;
left: 0px;
top: auto;
bottom: 10px;
right: auto;
height: 2px;
width: 14px;
background-color: #459be5;
}
.nav-img {
position: absolute;
top: 2px;

View File

@ -114,6 +114,14 @@ a:visited {
color: #898989;
}
a:hover {
color: #FF7500;
}
a:hover.fa {
color: #FF7500;
}
input,
textarea,
select {

View File

@ -97,6 +97,10 @@ a:visited {
color: #05101a;
}
a:hover {
color: #459be5;
}
ol,
ul,
li {

View File

@ -1,4 +1,3 @@
@charset "utf-8";
/* 头部 */
.header {
width: 100%;
@ -1272,7 +1271,7 @@ html body {
font-size: 14px;
line-height: 2.0;
background: #fafafa;
font-family: "Microsoft YaHei", "SimSun";
font-family: "微软雅黑", "宋体";
color: #05101a;
height: 100%;
position: relative;
@ -1308,7 +1307,6 @@ td,
span {
margin: 0;
padding: 0;
margin-bottom: 0px!important;
}
table,
@ -1365,6 +1363,10 @@ a:visited {
color: #05101a;
}
a:hover {
color: #459be5;
}
ol,
ul,
li {
@ -1471,7 +1473,7 @@ a.edu-txt-w80,
/*隐藏*/
.none {
display: none!important;
display: none
}
.block {
@ -1520,15 +1522,7 @@ a.edu-txt-w80,
.font-16 {
font-size: 16px !important;
}
.weight400{
font-weight: 400;
}
.weight500{
font-weight: 500;
}
.weight{
font-weight: bold;
}
.font-17 {
font-size: 17px !important;
}
@ -1548,9 +1542,6 @@ a.edu-txt-w80,
.font-25 {
font-size: 25px !important;
}
.font-26 {
font-size: 26px !important;
}
.font-24 {
font-size: 24px !important;
@ -1572,9 +1563,6 @@ a.edu-txt-w80,
font-size: 36px !important;
}
.font-40 {
font-size: 40px !important;
}
.font-50 {
font-size: 50px !important;
}
@ -1760,20 +1748,12 @@ a.decoration {
margin-bottom: 10px;
}
.mb12 {
margin-bottom: 12px;
}
.mb13 {
margin-bottom: 13px;
}
.mb14 {
margin-bottom: 14px;
}
.mb15 {
margin-bottom: 15px!important;
margin-bottom: 15px;
}
.mb16 {
@ -2345,9 +2325,6 @@ input::-ms-clear {
background-color: #F5F5F5;
}
.ant-modal-close{
top:8px!important;
}
.newContainer {
min-height: 100%;
@ -2367,6 +2344,10 @@ input::-ms-clear {
.newMain {
margin: 0 auto;
min-width: 1200px;
height: 100%;
min-height: 100%;
padding-top: 70px;
background-color: #fafafa;
}
@ -2444,23 +2425,13 @@ input::-ms-clear {
.color-grey-c {
color: #ccc !important;
}
a.hoverLine:hover{
text-decoration: underline;
}
.color-grey-cd {
color: #cdcdcd !important;
}
.color-grey-d {
color: #ddd;
}
.color-grey-9 {
color: #999 !important;
}
a:hover{
color: #466AFF !important;
color: #999999 !important;
}
.color-grey-98 {
@ -2495,23 +2466,33 @@ a:hover{
a.color-grey-name:hover,
a.color-dark:hover,
a.color-grey-6:hover,
a.color-grey-3:hover,a.color-ooo:hover {
color: #2A61FF !important;
a.color-grey-3:hover {
color: #4cacff !important;
}
a.color-grey-9:hover,
a.color-grey-8:hover,
a.color-grey-c:hover {
color: #111C24 !important;
}
/*蓝色*/
.color-blue {
color: #2A61FF !important;
}
.color-blue-file {
color: #4598FA!important;
color: #4CACFF !important;
}
/* 绿色 */
.color-green-file{
color: #28BD6C;
}
/*主*/
.color-blue_4C {
color: #4CACFF !important;
}
a.color-blue:hover,
a.color-blue_4C:hover {
color: #459BE6 !important;
}
/*橙色*/
.color-orange {
@ -2680,7 +2661,7 @@ a.color-green:hover {
/*百分比宽度*/
.width100 {
width: 100% !important;
width: 100%;
}
.width89 {
@ -3429,7 +3410,7 @@ a.user_bluebg_btn {
}
.cdefault {
cursor: default!important;
cursor: default
}
@ -3604,6 +3585,42 @@ a.user_bluebg_btn {
margin-right: 5px;
}
/*-------------------个人主页:右侧提示区域--------------------------*/
.-task-sidebar {
position: fixed;
width: 40px;
height: 180px;
right: 0;
bottom: 80px;
z-index: 10;
}
.-task-sidebar div {
height: 40px;
line-height: 40px;
box-sizing: border-box;
width: 40px;
background: #4CACFF;
color: #fff;
font-size: 20px;
text-align: center;
margin-bottom: 5px;
border-radius: 4px;
}
.-task-sidebar div i {
color: #fff;
}
.-task-sidebar div i:hover {
color: #fff !important;
}
.gotop {
background-color: rgba(208, 207, 207, 0.5) !important;
padding: 0px !important;
}
/***** loading ******/
/*****载入中******/
@ -3928,21 +3945,44 @@ html>body #ajax-indicator {
max-height: 340px;
}/*头部导航条样式---2018-03-19--by-cs*/
.privateTag{
display: block;
padding:0px 6px;
border-radius: 12px;
border:1px solid #2FC25B;
height: 18px;
line-height: 18px;
font-size: 12px;
margin-left: 10px;
color: #2FC25B;
.ant-dropdown.imgDropdown,.addDropdown{
z-index:10000!important;
width: 120px;
text-align: center;
padding: 0;
}
.addDropdown ul{
padding:0px;
}
.addDropdown ul a{
padding:0px;
margin:0px;
}
.imgDropdown li:first-child {
border-bottom: 1px solid #eee;
cursor: default;
}
.imgDropdown li:first-child:hover,.imgDropdown li:last-child:hover{
background-color: #fff;
}
.imgDropdown li:last-child:hover a{
color: #4CACFF;
}
.imgDropdown li:last-child {
border-top: 1px solid #eee;
}
.imgDropdown li,.addDropdown li {
height: 40px;
line-height: 40px;
padding: 0!important;
cursor: default;
text-align: center;
}
.head-nav {
text-align: center;
height: 70px;
height: 58px;
box-sizing: border-box;
min-width: 780px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -3953,25 +3993,25 @@ html>body #ajax-indicator {
position: absolute;
top: 0px;
z-index: 3;
height: 70px;
height: 100%;
box-sizing: border-box;
}
.head-nav ul#header-nav li {
float: left;
height: 70px;
line-height: 70px;
height: 58px;
line-height: 58px;
cursor: pointer;
position: relative;
font-size: 16px;
padding-right:40px;
padding:0px 20px;
}
.head-nav ul#header-nav li a {
display: block;
height: 100%;
width: 100%;
color: #333;
color: #fff;
font-size: 16px;
}
@ -4101,6 +4141,8 @@ em.vertical-line {
/* 右侧内容宽度变化的话需要调整posi-search right的值*/
/*底部*/
.footercon {
border-bottom: 1px solid #47494d;
@ -6697,18 +6739,75 @@ ul.count_ul li:not(:last-child):after {
input.ant-input-lg::placeholder{
font-size: 14px !important;
}
p{
margin-bottom: 0px!important;
}
.toprightNum{
.newFooter {
position: absolute;
right: 0px;
top:4px;
color: #999;
bottom: 0;
width: 100%;
background: #323232;
clear: both;
min-width: 1200px;
z-index: 8;
left: 0;
}
.ant-input, .ant-input .ant-input-suffix{
background-color: #fff!important;
.footEdition {
background-color: #171b23;
}
.has-error .ant-input{
background-color: #FEF1F0!important;
.footEdition .footContent {
display: flex;
align-items: flex-start;
padding: 86px 0;
justify-content: space-around;
width: 1200px;
margin: 0 auto;
}
.footEdition .footContent ul {
min-width: 120px;
text-align: left;
margin-right: 80px
}
.footEdition .footContent ul.center {
text-align: center;
}
.footEdition .footContent ul>img {
width: 100px;
height: 100%;
margin-bottom: 30px;
margin-top: 25px;
border-radius: 10px;
}
.footEdition .footContent ul li {
height: 20px;
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: #bdc2d1;
margin-bottom: 15px!important;
}
.footEdition .footContent ul li.thehead {
height: 25px;
font-size: 18px;
font-weight: 600;
color: #fff;
line-height: 25px;
margin-bottom: 20px!important;
}
.footEdition .footContent ul li a {
color: #bdc2d1!important;
}
.copyrightDesc {
font-size: 12px;
font-weight: 400;
color: #bdc2d1;
line-height: 28px;
padding: 15px 0;
text-align: center;
background-color: #1b212c;
}
.newFooter p {
margin-top: 0;
margin-bottom: 0!important;
}
.copyrightDesc a {
color: #bdc2d1!important;
}

File diff suppressed because one or more lines are too long

BIN
public/css/iconfont.eot Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

1526
public/css/iconfont.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,111 +0,0 @@
.CodeMirror-merge {
position: relative;
white-space: pre;
}
.CodeMirror-merge, .CodeMirror-merge .CodeMirror {
min-height:50px;
}
.CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 48%; }
.CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 4%; }
.CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; }
.CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; }
.CodeMirror-merge-pane {
display: inline-block;
white-space: normal;
vertical-align: top;
}
.CodeMirror-merge-pane-rightmost {
position: absolute;
right: 0px;
z-index: 1;
}
.CodeMirror-merge-gap {
z-index: 2;
display: inline-block;
height: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
position: relative;
background: #515151;
}
.CodeMirror-merge-scrolllock-wrap {
position: absolute;
bottom: 0; left: 50%;
}
.CodeMirror-merge-scrolllock {
position: relative;
left: -50%;
cursor: pointer;
color: #d8d8d8;
line-height: 1;
}
.CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right {
position: absolute;
left: 0; top: 0;
right: 0; bottom: 0;
line-height: 1;
}
.CodeMirror-merge-copy {
position: absolute;
cursor: pointer;
color: #ce374b;
z-index: 3;
}
.CodeMirror-merge-copy-reverse {
position: absolute;
cursor: pointer;
color: #44c;
}
.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; }
.CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; }
.CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
.CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
.CodeMirror-merge-r-chunk { background: #9a6868; }
.CodeMirror-merge-r-chunk-start { /*border-top: 1px solid #ee8; */}
.CodeMirror-merge-r-chunk-end {/* border-bottom: 1px solid #ee8; */}
.CodeMirror-merge-r-connect { fill:#9a6868;}
.CodeMirror-merge-l-chunk { background: #eef; }
.CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; }
.CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; }
.CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; }
.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; }
.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; }
.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; }
.CodeMirror-merge-collapsed-widget:before {
content: "(...)";
}
.CodeMirror-merge-collapsed-widget {
cursor: pointer;
color: #88b;
background: #eef;
border: 1px solid #ddf;
font-size: 90%;
padding: 0 3px;
border-radius: 4px;
}
.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
public/faviconold.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -1,16 +1,16 @@
<!DOCTYPE html>
<html lang="zh-CN" class="notranslate translated-ltr" translate="no">
<html lang="zh-CN">
<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="google" content="notranslate" />
<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">
<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/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">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/merge.css">
<%= htmlWebpackPlugin.tags.headTags %>
@ -21,7 +21,6 @@
<div id="root" class="page -layout-v -fit widthunit"></div>
<div id="picture_display" style="display: none;"></div>
<script src="%PUBLIC_URL%js/jquery-1.8.3.min.js"></script>
<script src="%PUBLIC_URL%js/js_min_all.js"></script>
<script src="%PUBLIC_URL%js/codemirror/codemirror.js"></script>
<script src="%PUBLIC_URL%js/editormd/editormd.min.js"></script>
<script src="%PUBLIC_URL%js/codemirror/merge/merge.js"></script>

View File

@ -3319,9 +3319,9 @@
text = text.replace(emailReg, function ($1, $2, $3, $4) {
return $1.replace(/@/g, "_#_&#64;_#_");
});
// " + editormd.urls.atLinkBase + "" + $2 + "
text = text.replace(atLinkReg, function ($1, $2) {
return "<span title=\"&#64;" + $2 + "\" class=\"at-link\"> " + $1 + " </span>";
return "<a href=\"" + editormd.urls.atLinkBase + "" + $2 + "\" title=\"&#64;" + $2 + "\" class=\"at-link\">" + $1 + "</a>";
}).replace(/_#_&#64;_#_/g, "@");
}

View File

@ -60,13 +60,12 @@ body {
.ant-progress-textno {
color: #f5222d;
}
.CodeMirror pre.CodeMirror-line{
font-size: 16px!important;
}
/* md多空格 */
.markdown-body p {
margin:10px 0px!important;
font-size: 16px !important;
line-height: 2 !important;
white-space: pre-wrap;
}
@ -88,10 +87,6 @@ body {
border-left: 1px solid rgb(221, 221, 221);
/* 某些情况下被cm盖住了 */
z-index: 99;
padding:8px 8px 50px;
}
.editormd-preview .markdown-body{
padding:0px !important;
}
/* 图片点击放大的场景,隐藏图片链接 */
@ -102,6 +97,9 @@ body {
.editormd-image-click-expand .editormd-image-dialog .image-link {
display: none;
}
.editormd-fullscreen{
z-index: 10000;
}
/* 解决鼠标框选时,左边第一列没高亮的问题 */
.CodeMirror .CodeMirror-lines pre.CodeMirror-line,

View File

@ -3,13 +3,20 @@ import './App.css';
import { ConfigProvider } from 'antd'
import zhCN from 'antd/lib/locale-provider/zh_CN';
import {
// BrowserRouter as Router,
BrowserRouter as Router,
Route,
Switch
Switch,
Redirect
} from 'react-router-dom';
import axios from 'axios';
import LoginDialog from './modules/login/LoginDialog';
import 'babel-polyfill';
import Notcompletedysl from './modules/user/Notcompletedysl';
import Trialapplicationysl from './modules/login/Trialapplicationysl';
import Trialapplicationreview from './modules/user/Trialapplicationreview';
import AccountProfile from "./modules/user/AccountProfile";
import Accountnewprofile from './modules/user/Accountnewprofile';
import Certifiedprofessional from './modules/modals/Certifiedprofessional';
import Loading from './Loading'
import Loadable from 'react-loadable';
@ -17,9 +24,10 @@ import marked from './common/marked';
import moment from 'moment'
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles';
import SiderBar from './forge/Component/SiderBar'
import { SnackbarHOC } from 'educoder';
import history from './history';
import { SnackbarHOC } from 'educoder'
import { initAxiosInterceptors } from './AppConfig'
import { Provider } from 'react-redux';
import configureStore from './redux/stores/configureStore';
@ -35,20 +43,10 @@ const theme = createMuiTheme({
},
});
//forge项目
const Projects = Loadable({
loader: () => import('./forge/Index'),
loading: Loading,
})
// forge项目详情
const ProjectDetail = Loadable({
loader: () => import("./forge/Main/DetailAdaptor"),
loading: Loading,
});
//forge安全设置
const Security = Loadable({
loader: () => import('./forge/SecuritySetting/Index'),
loading: Loading,
})
// const Projects = Loadable({
// loader: () => import('./forge/Index'),
// loading: Loading,
// })
//forge项目-devOps详情
const OpsDetail = Loadable({
loader: () => import('./forge/DevOps/opsDetail'),
@ -64,47 +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,
})
const EducoderLogin = Loadable({
loader: () => import('./modules/login/EducoderLogin'),
loading: Loading,
})
const Search = Loadable({
loader: () => import('./modules/search/'),
loading: Loading,
})
const WikiPreview = Loadable({
loader: () => import('./forge/Wiki/Preview'),
loading: Loading,
})
const ProjectIndex = Loadable({
loader: () => import("./forge/Index"),
loading: Loading,
});
// const CreateMerge = Loadable({
// loader: () => import('./forge/Merge/NewMerge'),
// loading: Loading,
// })
// 此处仅维护前端可能的一级路由,不用进行项目或者组织判断的字段。
const keyWord = ["explore", "settings", "setting", "mulan", "wiki", "issues", "setting", "trending", "code", "projects", "pulls", "mine", "login", "register", "email", "export", "nopage", "404", "403", "500", "501", "search", "organize"];
class App extends Component {
constructor(props) {
super(props);
@ -114,51 +89,6 @@ class App extends Component {
mydisplay: false,
occupation: 0,
mygetHelmetapi: null,
pathType: null,
pathName: null,
}
}
UNSAFE_componentWillMount() {
initAxiosInterceptors(this.props);
let pathname = window.location.pathname ? window.location.pathname.split('/')[1] : '';
pathname && this.getPathnameType(pathname);
// 添加路由监听,决定组织还是个人
this.unlisten = this.props.history.listen((location) => {
let newPathname = location.pathname.split('/')[1];
if (this.state.pathName !== newPathname) {
// this.setState({ pathType: '' });
newPathname && this.getPathnameType(newPathname);
}
});
}
shouldComponentUpdate(nextProps, nextState) {
// (!keyWord.includes(this.props.location.pathname.split('/')[1])) &&
if (nextProps.location.pathname.split('/')[1] !== this.props.location.pathname.split('/')[1] && nextState.pathType === this.state.pathType) {
return false;
} else {
return true;
}
}
getPathnameType = (pathname) => {
if (!keyWord.includes(pathname)) {
let url = `/owners/${pathname}.json`;
axios.get(url).then((response) => {
if (response && response.status === 200) {
this.setState({
pathType: response.data.type || '404',
pathName: pathname,
})
}
});
}else{
this.setState({
pathType: pathname,
pathName: pathname,
});
}
}
@ -175,22 +105,43 @@ class App extends Component {
Addcoursestypes: false
})
};
ModalCancelsy = () => {
this.setState({
mydisplay: false,
})
window.location.href = "/";
};
ModalshowCancelsy = () => {
this.setState({
mydisplay: true,
})
};
disableVideoContextMenu = () => {
window.$("body").on("mousedown", "video", function (event) {
if (event.which === 3) {
window.$('video').bind('contextmenu', function () { return false; });
} else {
window.$('video').unbind('contextmenu');
}
});
}
componentDidMount() {
document.title = "loading...";
this.disableVideoContextMenu();
history.listen(() => {
this.forceUpdate();
const $ = window.$
$("html").animate({ scrollTop: $('html').scrollTop() - 0 })
});
initAxiosInterceptors(this.props);
this.getAppdata();
window.addEventListener('error', (event) => {
const msg = `${event.type}: ${event.message}`;
});
}
componentWillUnmount() {
this.unlisten && this.unlisten(); // 执行解绑
}
//修改登录方法
Modifyloginvalue = () => {
this.setState({
@ -261,139 +212,49 @@ class App extends Component {
};
render() {
const { mygetHelmetapi, pathType} = this.state;
let personal = mygetHelmetapi && mygetHelmetapi.personal;
return (
<Provider store={store}>
<ConfigProvider locale={zhCN}>
<MuiThemeProvider theme={theme}>
<LoginDialog {...this.props} {...this.state} Modifyloginvalue={() => this.Modifyloginvalue()}></LoginDialog>
<SiderBar />
{/* <Router> */}
<Switch>
{/* wiki预览 */}
<Route path="/:owner/:projectsId/wiki/preview/:projectName/:projectId" render={
(props) => {
return (<WikiPreview {...this.props} {...props} {...this.state} />)
}
} />
{/* 项目PR */}
<Route path="/:owner/:projectsId/compare"
render={
(props) => (<ProjectDetail {...this.props} {...props} {...this.state} />)
}
></Route>
{/*项目*/}
<Route
path={"/:owner/:projectId/devops/:opsId/detail"}
render={
(props) => {
return (<OpsDetail {...this.props} {...props} {...this.state} />)
<Router>
<Switch>
<Route
path="/accounts/:login"
render={(props) => (
<Account {...this.props} {...this.state} {...props} />
)}
></Route>
{/* 论坛交流板块管理 */}
<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} />
{/*404*/}
<Route path="/nopage" component={Shixunnopage} />
<Redirect from="/projects" to="/nopage"/>
{/* 个人主页 */}
<Route exact path="/"
render={
(props) => (
<Forums {...this.props} {...this.state} {...props} />
)
}
}>
</Route>
<Route
path={"/settings"}
render={
(props) => {
return (<Security {...this.props} {...props} {...this.state} />)
}
}>
</Route>
<Route
path="/register"
render={
(props) => {
return (<EducoderLogin {...this.props} {...props} {...this.state} />)
}
}
/>
{/*403*/}
<Route path="/403" component={Shixunauthority} />
<Route path="/500" component={http500} />
{/*404*/}
<Route path="/nopage" component={Shixunnopage} />
{/* 查询 */}
<Route path="/search" component={Search} />
<Route exact path="/explore"
render={
(props) => (
<ProjectIndex {...this.props} {...props} />
)
}
/>
{/* 组织 */}
<Route path={"/organize"}
render={
(props) => {
return (<OrganizeIndex {...props} {...this.props} {...this.state} />)
}
}>
</Route>
{/*新建项目等*/}
<Route
path={"/projects"}
render={
(props) => {
return (<Projects {...this.props} {...props} {...this.state} />)
}
}>
</Route>
{/* 判断为用户/组织,并进入对应页面 */}
{
pathType === 'User' ?
<Route exact path="/:username"
render={
(props) => {
return (<InfosIndex {...this.props} {...this.state} />)
}
}
/> : pathType === 'Organization' ? <Route path={"/:OIdentifier"}
render={
(props) => {
return (<OrganizeIndex {...props} {...this.props} {...this.state} />)
}
}>
</Route> : pathType === '404' ? <Route component={Shixunnopage} />:
<Route exact path="/"
render={
(props) => (
personal && personal.length > 0 ?
<InfosIndex {...this.props} {...props} />
:
<ProjectIndex {...this.props} {...props} />
)
}
/>
// <Route path="/" component={Loading} />
// <Route path="/" component={Shixunnopage} />
}
{/* 个人主页 */}
<Route path="/:username"
render={
(props) => {
return (<InfosIndex {...this.props} {...this.state} />)
}
}></Route>
<Route component={Shixunnopage} />
</Switch>
{/* </Router> */}
/>
</Switch>
</Router>
</MuiThemeProvider>
</ConfigProvider>
</Provider>

View File

@ -2,17 +2,19 @@ import axios from 'axios';
import { requestProxy } from "./indexEduplus2RequestProxy";
import { broadcastChannelOnmessage, isDev, queryString } from 'educoder';
import { notification } from 'antd';
import cookie from 'react-cookies';
import './index.css';
let message501 = false;
broadcastChannelOnmessage('refreshPage', () => {
window.location.reload();
window.location.reload()
})
function locationurl(list) {
if (window.location.port !== "3007") {
window.location.href = list
if (window.location.port === "3007") {
} else {
window.location.href = list
}
}
// TODO 开发期多个身份切换
@ -24,22 +26,56 @@ if (isDev) {
parsed = queryString.parse(_search);
}
debugType = window.location.search.indexOf('debug=t') !== -1 ? 'teacher' :
window.location.search.indexOf('debug=s') !== -1 ? 'student' :
window.location.search.indexOf('debug=a') !== -1 ? 'admin' : parsed.debug || 'admin'
window.location.search.indexOf('debug=s') !== -1 ? 'student' :
window.location.search.indexOf('debug=a') !== -1 ? 'admin' : parsed.debug || 'admin'
}
function clearAllCookie() {
cookie.remove('_educoder_session', { path: '/' });
cookie.remove('autologin_trustie', { path: '/' });
setpostcookie()
}
clearAllCookie();
function setpostcookie() {
const str = window.location.pathname;
if (str.indexOf("/wxcode") !== -1) {
console.log("123")
cookie.remove('_educoder_session', { path: '/' });
cookie.remove('autologin_trustie', { path: '/' });
const _params = window.location.search;
if (_params) {
let _search = _params.split('?')[1];
let _educoder_sessions = _search.split('&')[0].split('=');
cookie.save('_educoder_session', _educoder_sessions[1], { domain: '.educoder.net', path: '/' });
let autologin_trusties = _search.split('&')[1].split('=');
cookie.save('autologin_trustie', autologin_trusties[1], { domain: '.educoder.net', path: '/' });
}
}
}
setpostcookie();
window._debugType = debugType;
export function initAxiosInterceptors(props) {
// 判断网络是否连接
initOnlineOfflineListener();
initOnlineOfflineListener()
// TODO 避免重复的请求 https://github.com/axios/axios#cancellation
var
proxy = "http://localhost:3000"
proxy = "https://testforum.trustie.net"
var proxy = "https://testforgeplus.trustie.net";
const requestMap = {};
window.setfalseInRequestMap = function (keyName) {
requestMap[keyName] = false;
}
//响应前的设置
axios.interceptors.request.use(
config => {
if(config.url.indexOf("http") !== -1) {
setpostcookie()
clearAllCookie()
if (config.url.indexOf(proxy) !== -1 || config.url.indexOf(':') !== -1) {
return config
}
requestProxy(config);
requestProxy(config)
let url = `/api${config.url}`;
if (`${config[0]}` !== `true`) {
@ -53,12 +89,18 @@ export function initAxiosInterceptors(props) {
} else {
config.url = url;
}
setpostcookie();
}
if (config.url.indexOf('update_file') === -1) {
requestMap[config.url] = true;
window.setTimeout("setfalseInRequestMap('" + config.url + "')", 900)
}
return config;
},
err => {
return Promise.reject(err);
});
});
axios.interceptors.response.use(function (response) {
if (response === undefined) {
@ -85,10 +127,7 @@ export function initAxiosInterceptors(props) {
}
if (response.data.status === 404) {
let responseURL = response.request ? response.request.responseURL:'';
if (responseURL.indexOf('/api/users/') === -1 && responseURL.indexOf('/api/organizations/') === -1 ) {
locationurl('/nopage');
}
locationurl('/nopage');
}
if (response.data.status === 500) {
@ -110,6 +149,8 @@ export function initAxiosInterceptors(props) {
message501 = false
}, 2000);
}
requestMap[response.config.url] = false;
setpostcookie();
return response;
}, function (error) {
return Promise.reject(error);

View File

@ -1,4 +1,7 @@
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import { Spin } from 'antd';
class Loading extends Component {
@ -9,7 +12,21 @@ class Loading extends Component {
}
render() {
return ""
// Loading
return (
<div className="App" style={{ minHeight: '800px', width: "100%" }}>
<style>
{
`
.margintop{
margin-top:20%;
}
`
}
</style>
<Spin size="large" className={"margintop"} />
</div>
);
}
}

View File

@ -1,5 +1,5 @@
import React, { useEffect , useState } from 'react';
import './header.scss';
import './Index.scss';
function Footer(){
const [ value , setValue ] = useState(undefined);
@ -19,8 +19,6 @@ function Footer(){
return(
<div>
<div style={{height:"543px"}}></div>
<div className="newFooter edu-txt-center">
{value && showhtml(value)}
{/* <div className="footerInfos">
<ul>
@ -59,8 +57,7 @@ function Footer(){
</ul>
</div>
<p className="footerCopy">© Copyright 2007~2021 国防科技大学Trustie团队 & IntelliDE <a href="https://beian.miit.gov.cn">湘ICP备 17009477</a></p> */}
</div>
</div>
</div>
)
}
export default Footer;

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

@ -0,0 +1,150 @@
.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;
}
}
}
}
.footEdition{
background-color: #171B23;
.footContent{
display: flex;
align-items: flex-start;
padding:86px 0px;
justify-content: space-around;
width: 1200px;
margin:0px auto;
ul{
min-width: 120px;
text-align: left;
margin-right: 80px;
&.center{
text-align: center;
}
&>p{
height: 22px;
font-size: 16px;
font-weight: 400;
color: #FFFFFF;
line-height: 22px;
}
&>img{
width: 100px;
height: 100%;
margin-bottom: 30px;
margin-top: 25px;
border-radius: 10px;
}
li{
height: 20px;
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: #BDC2D1;
margin-bottom: 15px!important;
a{
color: #BDC2D1!important;
&:hover{
text-decoration: underline;
}
}
&.thehead{
height: 25px;
font-size: 18px;
font-weight: 600;
color: #FFFFFF;
line-height: 25px;
margin-bottom: 20px!important;
}
}
.theline{
.imgCon{
width: 90px;
height: 90px;
padding:5px;
border-radius: 4px;
background-color: #fff;
img{
width: 100%;
border-radius: 3px;
}
}
}
}
}
}
.copyrightDesc{
font-size: 12px;
font-weight: 400;
color: #BDC2D1;
line-height: 28px;
padding:15px 0px;
text-align: center;
background-color: #1B212C;
a{
color: #BDC2D1!important;
}
}

View File

@ -195,7 +195,7 @@ class College extends Component {
align: 'center',
className: "edu-txt-center font-14 maxnamewidth105",
render: (text, record) => (
<a href={`/${record.login}`} title={record.name} target="_blank" className="task-hide maxnamewidth105" style={{
<a href={`/users/${record.login}`} title={record.name} target="_blank" className="task-hide maxnamewidth105" style={{
color:'#007bff',
}}> {

View File

@ -218,7 +218,7 @@ a:hover {
}
.color-blue {
color: #2A61FF;
color: #4CACFF;
}
.color-huang {

View File

@ -1,5 +1,4 @@
import moment from "moment";
import { number } from "prop-types";
// 处理整点 半点
// 取传入时间往后的第一个半点
@ -98,41 +97,3 @@ export function formatDuring(mss){
}
return days + "天" + hours + "小时" + minutes + "分";
}
/*
返回多久以前
backDate以前的某个日期
*/
export function timeAgo(backDate) {
try {
moment(backDate);
} catch (e) {
return '刚刚';
}
if(typeof backDate ==='number'){
backDate=backDate*1000
}else{
backDate= moment(backDate);
}
let time = new Date() - backDate;
var days = Math.floor(time / (1000 * 60 * 60 * 24));
var hours = Math.floor((time % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((time % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((time % (1000 * 60 * 60)) / 1000);
if (time <= 0) {
return "刚刚";
}
if (days) {
return days + "天前";
}
if (hours) {
return hours + "小时前";
}
if (minutes) {
return minutes + "分前";
}
if (seconds) {
return seconds + "秒前";
}
return "刚刚";
}

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

View File

@ -69,7 +69,7 @@ export function appendFileSizeToUploadFile(item) {
}
export function appendFileSizeToUploadFileAll(fileList) {
return fileList.map(item => {
if (item.name.indexOf(uploadNameSizeSeperator) === -1) {
if (item.name.indexOf(uploadNameSizeSeperator) == -1) {
return Object.assign({}, item, { name: `${item.name}${uploadNameSizeSeperator}${bytesToSize(item.size)}` })
}
return item

View File

@ -8,46 +8,15 @@ const isDev = window.location.port == 3007;
const isdev2= window.location.hostname ==='www.educoder.net'
export const TEST_HOST = "https://testforgeplus.trustie.net/"
export function getImageUrl(path) {
// https://www.educoder.net
// https://testbdweb.trustie.net
// const local = 'http://localhost:3000'
const local = 'https://testforgeplus.trustie.net';
const reg = /(http|https):\/\/([\w.]+\/?)\S*/;
if(reg.test(path)){
return path;
}
const local = 'https://testforgeplus.trustie.net'
if (isDev) {
return `${local}/${path}`
}
return `${path}`;
}
export function numFormat(num, digits){
let d = digits || 1;
var si = [
{ value: 1, symbol: "" },
{ value: 1E3, symbol: "k" },
{ value: 1E4, symbol: "W" }
];
var rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
var i;
for (i = si.length - 1; i > 0; i--) {
if (num >= si[i].value) {
break;
}
}
return (num / si[i].value).toFixed(d).replace(rx, "$1") + si[i].symbol;
}
export function getImage(path) {
// https://www.educoder.net
// https://testbdweb.trustie.net
// const local = 'http://localhost:3000'
const local = 'https://testforgeplus.trustie.net/';
if(path.indexOf("http://")===-1){
if (isDev) {
return `${local}/images/${path}`
}
return `/${path}`;
}else{
return path;
}
return `/${path}`;
}
export function getcdnImageUrl(path) {
@ -179,28 +148,28 @@ export function getmyUrl(geturl) {
}
export function getUploadActionUrl(path, goTest) {
return `${getUrl()}/api/attachments.json${isDev ?`${isDev ?`?debug=${window._debugType || 'admin'}` : ""}` : ""}`;
return `${getUrl()}/api/attachments.json?debug=${window._debugType || 'admin'}`;
}
export function getUploadLogoActionUrl() {
return `${getUrl()}/api/resumes/logo.json${isDev ?`?debug=${window._debugType || 'admin'}` : ""}`;
return `${getUrl()}/api/resumes/logo.json?debug=${window._debugType || 'admin'}`;
}
export function getUploadActionUrltwo(id) {
return `${getUrlmys()}/api/shixuns/${id}/upload_data_sets.json${isDev ?`?debug=${window._debugType || 'admin'}` : ""}`
return `${getUrlmys()}/api/shixuns/${id}/upload_data_sets.json?debug=${window._debugType || 'admin'}`
}
export function getUploadActionUrlthree() {
return `${getUrlmys()}/api/jupyters/import_with_tpm.json${isDev ?`?debug=${window._debugType || 'admin'}` : ""}`
return `${getUrlmys()}/api/jupyters/import_with_tpm.json?debug=${window._debugType || 'admin'}`
}
export function getupload_git_file(id) {
return `${getUrlmys()}/api/shixuns/${id}/upload_git_file.json${isDev ?`?debug=${window._debugType || 'admin'}` : ""}`
return `${getUrlmys()}/api/shixuns/${id}/upload_git_file.json?debug=${window._debugType || 'admin'}`
}
export function getUploadActionUrlOfAuth(id) {
return `${getUrl()}/api/users/accounts/${id}/auth_attachment.json${isDev ?`?debug=${window._debugType || 'admin'}` : ""}`
return `${getUrl()}/api/users/accounts/${id}/auth_attachment.json?debug=${window._debugType || 'admin'}`
}
export function getRandomNumber(type) {
@ -250,3 +219,15 @@ export function publicSearchs(Placeholder,onSearch,onInputs,onChanges,loadings)
allowClear={true}
></Search>)
}
// 手动添加/修改mate标签
export function addMeta(name, content){
if(document.querySelector(`meta[name='${name}']`)){
document.querySelector(`meta[name='${name}']`).content=content;
}else{
const meta = document.createElement('meta');
meta.content = content;
meta.name = name;
document.getElementsByTagName('head')[0].appendChild(meta);
}
};

View File

@ -64,7 +64,7 @@ function CommentItem({
const commentAvatar = (author) => (
<img
className="item-flex flex-image"
src={author.image_url ? getImageUrl(`/${author.image_url}`) : 'https://b-ssl.duitang.com/uploads/item/201511/13/20151113110434_kyReJ.jpeg'}
src={author.image_url ? getImageUrl(`images/${author.image_url}`) : 'https://b-ssl.duitang.com/uploads/item/201511/13/20151113110434_kyReJ.jpeg'}
alt=""
/>
);

View File

@ -38,11 +38,13 @@ export const formatDelta = (deltas) => {
alt="${alt}"
/>
`;
// text = "<img src="+url+" width='60px' height='30px' onclick='' alt="+alt+"/>";
}
}
formatted.push(text);
});
console.log(formatted);
return formatted.join('');
}

View File

@ -3,10 +3,10 @@
// export { default as OrderStateUtil } from '../routes/Order/components/OrderStateUtil';
export {
getUploadLogoActionUrl as getUploadLogoActionUrl,numFormat as numFormat,
getImageUrl as getImageUrl,getImage as getImage, getmyUrl as getmyUrl, getRandomNumber as getRandomNumber, getUrl as getUrl, publicSearchs as publicSearchs, getRandomcode as getRandomcode, getUrlmys as getUrlmys, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
getUploadLogoActionUrl as getUploadLogoActionUrl,
getImageUrl as getImageUrl, getmyUrl as getmyUrl, getRandomNumber as getRandomNumber, getUrl as getUrl, publicSearchs as publicSearchs, getRandomcode as getRandomcode, getUrlmys as getUrlmys, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
, getUploadActionUrl as getUploadActionUrl, getUploadActionUrltwo as getUploadActionUrltwo, getUploadActionUrlthree as getUploadActionUrlthree, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
, getTaskUrlById as getTaskUrlById, TEST_HOST, htmlEncode as htmlEncode, getupload_git_file as getupload_git_file, getcdnImageUrl as getcdnImageUrl
, getTaskUrlById as getTaskUrlById, TEST_HOST, htmlEncode as htmlEncode, getupload_git_file as getupload_git_file, getcdnImageUrl as getcdnImageUrl, addMeta as addMeta
} from './UrlTool';
export { setmiyah as setmiyah } from './Component';
@ -27,7 +27,7 @@ export {
markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension,
downloadFile, sortDirections, validateLength, mdJSONParse, exportMdtoHtml
} from './TextUtil'
export { handleDateString, getNextHalfHourOfMoment, formatDuring, formatSeconds ,timeAgo} from './DateUtil'
export { handleDateString, getNextHalfHourOfMoment, formatDuring, formatSeconds } from './DateUtil'
export { configShareForIndex, configShareForPaths, configShareForShixuns, configShareForCourses, configShareForCustom } from './util/ShareUtil'
@ -76,4 +76,10 @@ export { default as AliyunUploader } from './components/media/AliyunUploader'
export { default as ImageLayer2 } from './hooks/ImageLayer2'
// 外部
export { default as CBreadcrumb } from '../modules/courses/common/CBreadcrumb'
export { CNotificationHOC as CNotificationHOC } from '../modules/courses/common/CNotificationHOC'
export { default as ModalWrapper } from '../modules/courses/common/ModalWrapper'
export { default as NoneData } from '../modules/courses/coursesPublic/NoneData'
export { default as WordNumberTextarea } from '../modules/modals/WordNumberTextarea'

View File

@ -23,6 +23,7 @@ function indentCodeCompensation(raw, text) {
.join('\n');
}
//兼容之前的 ##标题式写法
let toc = []
let ctx = ["<ul>"]
@ -117,6 +118,7 @@ function replace_math_with_ids(text) {
return rs
}
const original_listitem = renderer.listitem
renderer.listitem = function (text, task, checked) {
return original_listitem(replace_math_with_ids(text), task, checked)
@ -155,7 +157,8 @@ renderer.heading = function (text, level, raw) {
level: level,
text: text
})
return '<h' + level + ' id="' + anchor + '">' + text + '</h' + level + '>'
let id = anchor.replace(/[.,/#!$%^&*;:{}=\-_`~():,。¥;「」|?》《~·【】‘、!]/g,"");
return '<h' + level + ' id="' + id + '" class="markdown_anchors"><a name="#'+id+'" class="anchors"><i class="iconfont icon-lianjieicon font-14"></i></a>' + text + '</h' + level + '>'
}
marked.setOptions({
silent: true,

170
src/common/marked1.js Normal file
View File

@ -0,0 +1,170 @@
import marked from 'marked'
import { escape } from 'marked/src/helpers'
function indentCodeCompensation(raw, text) {
const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
if (matchIndentToCode === null) {
return text;
}
const indentToCode = matchIndentToCode[1];
return text
.split('\n')
.map(node => {
const matchIndentInNode = node.match(/^\s+/);
if (matchIndentInNode === null) {
return node;
}
const [indentInNode] = matchIndentInNode;
if (indentInNode.length >= indentToCode.length) {
return node.slice(indentToCode.length);
}
return node;
})
.join('\n');
}
//兼容之前的 ##标题式写法
let toc = []
let ctx = ["<ul>"]
const renderer = new marked.Renderer()
const headingRegex = /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/
export function cleanToc() {
toc = []
ctx = ["<ul>"]
}
function buildToc(coll, k, level, ctx) {
if (k >= coll.length || coll[k].level <= level) { return k }
var node = coll[k]
ctx.push("<li><a href='#" + node.anchor + "'>" + node.text + "</a>")
k++
var childCtx = []
k = buildToc(coll, k, node.level, childCtx)
if (childCtx.length > 0) {
ctx.push("<ul>")
childCtx.forEach(function (idm) {
ctx.push(idm)
});
ctx.push("</ul>")
}
ctx.push("</li>")
k = buildToc(coll, k, level, ctx)
return k
}
export function getTocContent() {
buildToc(toc, 0, 0, ctx)
ctx.push("</ul>")
return ctx.join("")
}
const tokenizer = {
heading(src) {
const cap = headingRegex.exec(src)
if (cap) {
return {
type: 'heading',
raw: cap[0],
depth: cap[1].length,
text: cap[2]
}
}
},
fences(src) {
const cap = this.rules.block.fences.exec(src)
if (cap) {
const raw = cap[0]
let text = indentCodeCompensation(raw, cap[3] || '')
const lang = cap[2] ? cap[2].trim() : cap[2]
if (['latex', 'katex', 'math'].indexOf(lang) >= 0) {
const id = next_id()
const expression = text
text = id
math_expressions[id] = { type: 'block', expression }
}
return {
type: 'code',
raw,
lang,
text
}
}
}
}
const latexRegex = /(?:\${2})([^\n`]+?)(?:\${2})/gi
let katex_count = 0
const next_id = () => `__special_katext_id_${katex_count++}__`
let math_expressions = {}
export function getMathExpressions() {
return math_expressions
}
export function resetMathExpressions() {
katex_count = 0
math_expressions = {}
}
function replace_math_with_ids(text) {
let rs = text.replace(latexRegex, (_match, expression) => {
const id = next_id()
math_expressions[id] = { type: 'inline', expression }
return id
})
return rs
}
const original_listitem = renderer.listitem
renderer.listitem = function (text, task, checked) {
return original_listitem(replace_math_with_ids(text), task, checked)
}
const original_paragraph = renderer.paragraph
renderer.paragraph = function (text) {
return original_paragraph(replace_math_with_ids(text))
}
const original_tablecell = renderer.tablecell
renderer.tablecell = function (content, flags) {
return original_tablecell(replace_math_with_ids(content), flags)
}
renderer.code = function (code, infostring, escaped) {
const lang = (infostring || '').match(/\S*/)[0];
if (!lang) {
return '<pre class="prettyprint linenums"><code>'
+ (escaped ? code : escape(code, true))
+ '</code></pre>';
}
if (['latex', 'katex', 'math'].indexOf(lang) >= 0) {
return `<p class='editormd-tex'>${code}</p>`
} else {
return `<pre class="prettyprint linenums"><code class="language-${infostring}">${escaped ? code : escape(code, true)}</code></pre>\n`
}
}
renderer.heading = function (text, level, raw) {
let anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w\\u4e00-\\u9fa5]]+/g, '-');
toc.push({
anchor: anchor,
level: level,
text: text
})
return '<h' + level + ' id="' + anchor + '">' + text + '</h' + level + '>'
}
marked.setOptions({
silent: true,
smartypants: true,
gfm: true,
pedantic: false
})
marked.use({ tokenizer, renderer });
export default marked

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

@ -1,12 +1,16 @@
import React, { useEffect, useRef, useMemo } from 'react'
import 'katex/dist/katex.min.css'
import React, { useEffect, useRef, useMemo , useState } from 'react'
import 'katex/dist/katex.min.css';
import marked, { getTocContent, cleanToc, getMathExpressions, resetMathExpressions } from '../common/marked';
import 'code-prettify'
import 'code-prettify';
import dompurify from 'dompurify';
// import { getEmoji } from '../forge/Main/emoji';
import axios from 'axios';
import { renderToString } from 'katex'
const preRegex = /<pre[^>]*>/g
const preRegex = /<pre[^>]*>/g;
const strRegexSub = /:([a-zA-Z_]+):/g;
const quoteRegex = /\[[#][0-9]{0,}\]\(\/(.*?)\/(.*?)\/issues\/[0-9]{0,}\)/g;
function _unescape(str) {
let div = document.createElement('div')
div.innerHTML = str
@ -19,8 +23,29 @@ export default ({
value = '',
className,
style = {},
url
url,
owner=undefined,
projectsId=undefined
}) => {
const [ issues , setIssues ] = useState([]);
useEffect(()=>{
if(owner&&projectsId){
getIssueList();
}
},[owner,projectsId])
function getIssueList(){
axios.get(`/v1/${owner}/${projectsId}/issues`,{params:{
only_name:true,sort_direction:"desc",sort_by:"issues.created_on"
}}).then(result=>{
if(result){
let data = result.data.issues;
setIssues(data);
}
})
}
let str = String(value);
const html = useMemo(() => {
let rs = marked(str);
@ -29,14 +54,42 @@ export default ({
rs = rs.replace("<p>[TOC]</p>", getTocContent())
cleanToc()
}
// emoji
// let matchStr = str.match(strRegexSub);
// if(matchStr && matchStr.length>0){
// for(var i=0;i < matchStr.length;i++){
// rs = rs.replace(matchStr[i],getEmoji(matchStr[i]));
// }
// }
if(owner && projectsId && issues && issues.length>0){
let matchQuote = str.match(quoteRegex);
if(matchQuote && matchQuote.length>0){
let getIndexReg = /(?<=#)(.+?)(?=\])/g;
for(var x=0;x<matchQuote.length;x++){
let getIndex = matchQuote[x].match(getIndexReg);
if(getIndex && getIndex.length>0 && getIndex[0]){
let index = getIndex[0];
let filter = issues.filter(f=>f.project_issues_index.toString() === index);
if(filter && filter.length === 1){
let content = `#${index}:${filter[0].subject}`;
rs = rs.replace(`#${index}`,content);
}else{
let content = `<span>#${index}(已删除)</span>`;
rs = rs.replace(`<a href="`+`/${owner}/${projectsId}/issues/${index}`+`">#${index}</a>`,content);
}
}
}
}
}
rs = rs.replace(/(__special_katext_id_\d+__)/g, (_match, capture) => {
const { type, expression } = math_expressions[capture]
const { type, expression } = math_expressions[capture];
return renderToString(_unescape(expression) || '', { displayMode: type === 'block', throwOnError: false, output: 'html' })
})
rs = rs.replace(/▁/g, "▁▁▁")
resetMathExpressions()
return dompurify.sanitize(rs)
}, [str]);
}, [str,issues]);
// #id
useEffect(()=>{
@ -46,7 +99,7 @@ export default ({
let id = decodeURIComponent(u.split("#")[1]);
let ele = document.getElementById(id);
if(ele){
window.scrollTo(0, ele.offsetTop + 120);
window.scrollTo(0, ele.offsetTop);
}
}
}

View File

@ -435,11 +435,11 @@ class TPIContextProvider extends Component {
image_url: "avatars/User/1"
login: "innov"
name: "Coder"
user_url: "/innov"
user_url: "/users/innov"
*/
let user = resData.user;
user.username = resData.user.name;
user.user_url = `/${resData.user.login}`;
user.user_url = `/users/${resData.user.login}`;
// user.image_url = resData.image_url;
user.is_teacher = resData.is_teacher;
resData.user = user;

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

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