Compare commits
61 Commits
Author | SHA1 | Date |
---|---|---|
黄心宇 | e414343db3 | |
黄心宇 | 06374112d8 | |
黄心宇 | f45d31b489 | |
黄心宇 | 0580fcb2c0 | |
黄心宇 | baed9df260 | |
caishi | db9bfa7576 | |
caishi | c8a08bbd7f | |
caishi | 82b979894b | |
caishi | 7b430d8257 | |
caishi | 0accc419de | |
caishi | 7423036bb0 | |
caishi | 5e70812ef1 | |
“xxq250” | 58b8fb4ca8 | |
caishi | b56f0b27c2 | |
caishi | ae1f4246e4 | |
caishi | 75f26ac136 | |
caishi | 765840f2c8 | |
caishi | 5b4c103ad5 | |
caishi | 0ce87362d5 | |
xxq250 | 77730321ee | |
caishi | 703c876c18 | |
caishi | 471b8dff3b | |
caishi | 68501898bd | |
caishi | d96611893f | |
caishi | cf1905bc4b | |
caishi | fc5c957bf1 | |
caishi | 8401a55e49 | |
caishi | ee6961d02e | |
caishi | 57ab5d1199 | |
caishi | 6d9af5c906 | |
caishi | e38508ea59 | |
caishi | 68d1b69a09 | |
caishi | aef1451455 | |
caishi | 191e79e05a | |
caishi | ea8b5ece1a | |
caishi | 41e9326174 | |
caishi | 8dad3b231c | |
caishi | 0703fb1bb6 | |
caishi | 7a96f7021a | |
caishi | 54fc16ddad | |
caishi | c04b2ed5f6 | |
sylor_huang@126.com | 107b6ed95e | |
sylor_huang@126.com | 67ccc2ebe5 | |
sylor_huang@126.com | c00933b0ca | |
sylor_huang@126.com | 4f67719dac | |
sylor_huang@126.com | 5be45528cf | |
sylor_huang@126.com | c3b2f8f6ad | |
sylor_huang@126.com | 6950c1a820 | |
sylor_huang@126.com | b5881d5162 | |
sylor_huang@126.com | 4de25d36a8 | |
sylor_huang@126.com | 5992e83c60 | |
caishi | c740dcc6f8 | |
caishi | 9922b109f8 | |
sylor_huang@126.com | 7b097149eb | |
caishi | f7fa5291c6 | |
caishi | 8ebac42d42 | |
caishi | 10db5271d7 | |
sylor_huang@126.com | 4596eac895 | |
caishi | d1232be557 | |
sylor_huang@126.com | 79c943d31b | |
caishi | 6d8834403a |
16
.babelrc
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react",
|
||||
"stage-2"
|
||||
],
|
||||
"plugins": [[
|
||||
"transform-runtime",
|
||||
{
|
||||
"helpers": false,
|
||||
"polyfill": false,
|
||||
"regenerator": true,
|
||||
"moduleName": "babel-runtime"
|
||||
}
|
||||
]]
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
124
LICENSE
|
@ -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 License,Version 2
|
||||
Mulan Permissive Software License,Version 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 IT’S 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 License,Version 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.
|
16
README.md
|
@ -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>
|
|
@ -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,
|
||||
|
|
38
package.json
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -114,6 +114,14 @@ a:visited {
|
|||
color: #898989;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #FF7500;
|
||||
}
|
||||
|
||||
a:hover.fa {
|
||||
color: #FF7500;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
|
|
|
@ -97,6 +97,10 @@ a:visited {
|
|||
color: #05101a;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #459be5;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
li {
|
||||
|
|
|
@ -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;
|
||||
}
|
After Width: | Height: | Size: 584 KiB |
|
@ -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; }
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 8.8 KiB |
|
@ -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>
|
||||
|
|
|
@ -3319,9 +3319,9 @@
|
|||
text = text.replace(emailReg, function ($1, $2, $3, $4) {
|
||||
return $1.replace(/@/g, "_#_@_#_");
|
||||
});
|
||||
// " + editormd.urls.atLinkBase + "" + $2 + "
|
||||
|
||||
text = text.replace(atLinkReg, function ($1, $2) {
|
||||
return "<span title=\"@" + $2 + "\" class=\"at-link\"> " + $1 + " </span>";
|
||||
return "<a href=\"" + editormd.urls.atLinkBase + "" + $2 + "\" title=\"@" + $2 + "\" class=\"at-link\">" + $1 + "</a>";
|
||||
}).replace(/_#_@_#_/g, "@");
|
||||
}
|
||||
|
||||
|
|
12
src/App.css
|
@ -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,
|
||||
|
|
327
src/App.js
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
||||
}}> {
|
||||
|
|
|
@ -218,7 +218,7 @@ a:hover {
|
|||
}
|
||||
|
||||
.color-blue {
|
||||
color: #2A61FF;
|
||||
color: #4CACFF;
|
||||
}
|
||||
|
||||
.color-huang {
|
||||
|
|
|
@ -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 "刚刚";
|
||||
}
|
|
@ -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;
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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=""
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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('');
|
||||
}
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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: "微软雅黑","宋体";
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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">只支持JPG、PNG、JPEG大小不超过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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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">只支持JPG、PNG、JPEG大小不超过2M建议尺寸4:3</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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 6.0 KiB |